Skip to content
This repository has been archived by the owner on Dec 13, 2021. It is now read-only.

Fast property accessors #159

Merged
merged 2 commits into from
Apr 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ We also have a [MyGet package repository](https://www.myget.org/gallery/umbraco-

If you prefer, you can compile Ditto yourself, you'll need:

* Visual Studio 2012 (or above)
* [Visual Studio 2012 (or above, including Community Editions)](https://www.visualstudio.com/downloads/)
* Microsoft Build Tools 2013 (aka [MSBuild 12](https://www.microsoft.com/en-us/download/details.aspx?id=40760))

To clone it locally run the following git commands:

Expand Down
6 changes: 6 additions & 0 deletions src/Our.Umbraco.Ditto.PerformanceTests/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
</startup>
</configuration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{7409ADFA-56F9-4A18-9208-0EEBC696F46B}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Our.Umbraco.Ditto.PerformanceTests</RootNamespace>
<AssemblyName>Umbraco.PerformanceTests</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="BenchmarkDotNet, Version=0.9.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\BenchmarkDotNet.0.9.4\lib\net45\BenchmarkDotNet.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Build" />
<Reference Include="Microsoft.Build.Framework" />
<Reference Include="Microsoft.Build.Utilities.v12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Management" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Our.Umbraco.Ditto\Our.Umbraco.Ditto.csproj">
<Project>{3a2482a4-ad19-4c26-a449-4287da07ab16}</Project>
<Name>Our.Umbraco.Ditto</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
105 changes: 105 additions & 0 deletions src/Our.Umbraco.Ditto.PerformanceTests/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.ComponentModel;
using System.Reflection;

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;

namespace Our.Umbraco.Ditto.PerformanceTests
{
public class Program
{
public class DittoPerformance
{
private DittoPerformance obj;

private dynamic dlr;
private PropertyInfo prop;
private PropertyDescriptor descriptor;

public string Value { get; set; }

public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<DittoPerformance>(new Config());
Console.WriteLine();

// Display a summary to match the output of the original Performance test
foreach (var report in summary.Reports.OrderBy(r => r.Key.Target.MethodTitle))
{
Console.WriteLine("{0}: {1:N2} ns", report.Key.Target.MethodTitle, report.Value.ResultStatistics.Median);
}

Console.WriteLine();
}

[Setup]
public void Setup()
{
this.obj = new DittoPerformance();
this.dlr = this.obj;
this.prop = typeof(DittoPerformance).GetProperty("Value");
this.descriptor = TypeDescriptor.GetProperties(this.obj)["Value"];
}

[Benchmark(Description = "1. Static C#", Baseline = true)]
public string StaticCSharp()
{
this.obj.Value = "abc";
return this.obj.Value;
}

[Benchmark(Description = "2. Dynamic C#")]
public string DynamicCSharp()
{
this.dlr.Value = "abc";
return this.dlr.Value;
}

[Benchmark(Description = "3. PropertyInfo")]
public string PropertyInfo()
{
this.prop.SetValue(this.obj, "abc", null);
return (string)this.prop.GetValue(this.obj, null);
}

[Benchmark(Description = "4. PropertyDescriptor")]
public string PropertyDescriptor()
{
this.descriptor.SetValue(this.obj, "abc");
return (string)this.descriptor.GetValue(this.obj);
}

[Benchmark(Description = "5. PropertyInfoInvocations")]
public string PropertyInvocations()
{
PropertyInfoInvocations.SetValue(this.prop, this.obj, "abc");
return (string)PropertyInfoInvocations.GetValue(this.prop, this.obj);
}
}

// BenchmarkDotNet settings (you can use the defaults, but these are tailored for this benchmark)
private class Config : ManualConfig
{
public Config()
{
this.Add(Job.Default.WithLaunchCount(1));
this.Add(PropertyColumn.Method);
this.Add(StatisticColumn.Median, StatisticColumn.StdDev);
this.Add(BaselineDiffColumn.Scaled);
this.Add(CsvExporter.Default, MarkdownExporter.Default, MarkdownExporter.GitHub);
this.Add(new ConsoleLogger());
}
}
}
}
16 changes: 16 additions & 0 deletions src/Our.Umbraco.Ditto.PerformanceTests/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Reflection;
using System.Runtime.InteropServices;

[assembly: AssemblyTitle("Our.Umbraco.Ditto.PerformanceTests")]
[assembly: AssemblyDescription("Performance tests for Ditto")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Umbraco Community")]
[assembly: AssemblyProduct("Ditto")]
[assembly: AssemblyCopyright("Copyright \xa9 Umbraco Community 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

[assembly: ComVisible(false)]
[assembly: Guid("7409ADFA-56F9-4A18-9208-0EEBC696F46B")]

[assembly: AssemblyVersion("1.0.*")]
4 changes: 4 additions & 0 deletions src/Our.Umbraco.Ditto.PerformanceTests/packages.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="BenchmarkDotNet" version="0.9.4" targetFramework="net45" />
</packages>
10 changes: 9 additions & 1 deletion src/Our.Umbraco.Ditto.sln
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.Umbraco.Ditto", "Our.Umbraco.Ditto\Our.Umbraco.Ditto.csproj", "{3A2482A4-AD19-4C26-A449-4287DA07AB16}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6CF4E7DA-6359-479E-A64D-2023C743BA1C}"
Expand Down Expand Up @@ -39,6 +41,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentat
..\docs\usage-basic.md = ..\docs\usage-basic.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Our.Umbraco.Ditto.PerformanceTests", "Our.Umbraco.Ditto.PerformanceTests\Our.Umbraco.Ditto.PerformanceTests.csproj", "{7409ADFA-56F9-4A18-9208-0EEBC696F46B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -53,6 +57,10 @@ Global
{8F71EB63-C2E5-4391-8FFC-6EEF6BD9BD54}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8F71EB63-C2E5-4391-8FFC-6EEF6BD9BD54}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8F71EB63-C2E5-4391-8FFC-6EEF6BD9BD54}.Release|Any CPU.Build.0 = Release|Any CPU
{7409ADFA-56F9-4A18-9208-0EEBC696F46B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7409ADFA-56F9-4A18-9208-0EEBC696F46B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7409ADFA-56F9-4A18-9208-0EEBC696F46B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7409ADFA-56F9-4A18-9208-0EEBC696F46B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
39 changes: 39 additions & 0 deletions src/Our.Umbraco.Ditto/Common/CachedInvocations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;

namespace Our.Umbraco.Ditto
{
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;

internal class CachedInvocations
{
/// <summary>
/// The method cache for storing function implementations.
/// </summary>
protected static readonly ConcurrentDictionary<MethodBaseCacheItem, Func<object, object>> FunctionCache
= new ConcurrentDictionary<MethodBaseCacheItem, Func<object, object>>();

/// <summary>
/// The method cache for storing action implementations.
/// </summary>
protected static readonly ConcurrentDictionary<MethodBaseCacheItem, Action<object, object>> ActionCache
= new ConcurrentDictionary<MethodBaseCacheItem, Action<object, object>>();

/// <summary>
/// Returns a cache key for the given method and type.
/// </summary>
/// <param name="type">
/// The <see cref="object"/> the key reflects.
/// </param>
/// <param name="memberName">
/// The method name. Generated at compile time.
/// </param>
/// <returns>
/// The <see cref="MethodBaseCacheItem"/> for the given method and type.
/// </returns>
protected static MethodBaseCacheItem GetMethodCacheKey(object type, [CallerMemberName] string memberName = null)
{
return new MethodBaseCacheItem(memberName, type);
}
}
}
39 changes: 8 additions & 31 deletions src/Our.Umbraco.Ditto/Common/EnumerableInvocations.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
Expand All @@ -14,14 +13,9 @@ namespace Our.Umbraco.Ditto
/// Once a method is invoked for a given type then it is cached so that subsequent calls do not require
/// any overhead compilation costs.
/// </summary>
internal static class EnumerableInvocations
// ReSharper disable once ClassNeverInstantiated.Global
internal class EnumerableInvocations : CachedInvocations
{
/// <summary>
/// The method cache for storing function implementations.
/// </summary>
private static readonly ConcurrentDictionary<MethodBaseCacheItem, Func<object, object>> Cache
= new ConcurrentDictionary<MethodBaseCacheItem, Func<object, object>>();

/// <summary>
/// The cast method.
/// </summary>
Expand Down Expand Up @@ -61,10 +55,10 @@ public static object Cast(Type type, IEnumerable value)
var key = GetMethodCacheKey(type);

Func<object, object> f;
if (!Cache.TryGetValue(key, out f))
if (!FunctionCache.TryGetValue(key, out f))
{
f = StaticMethodSingleParameter<object>(CastMethod.MakeGenericMethod(type));
Cache[key] = f;
FunctionCache[key] = f;
}

return f(value);
Expand All @@ -85,10 +79,10 @@ public static object Empty(Type type)
var key = GetMethodCacheKey(type);

Func<object, object> f;
if (!Cache.TryGetValue(key, out f))
if (!FunctionCache.TryGetValue(key, out f))
{
f = StaticMethod<object>(EmptyMethod.MakeGenericMethod(type));
Cache[key] = f;
FunctionCache[key] = f;
}

return f(type);
Expand All @@ -109,10 +103,10 @@ public static object FirstOrDefault(Type type, IEnumerable value)
var key = GetMethodCacheKey(type);

Func<object, object> f;
if (!Cache.TryGetValue(key, out f))
if (!FunctionCache.TryGetValue(key, out f))
{
f = StaticMethodSingleParameter<object>(FirstOrDefaultMethod.MakeGenericMethod(type));
Cache[key] = f;
FunctionCache[key] = f;
}

return f(Cast(type, value));
Expand Down Expand Up @@ -168,22 +162,5 @@ public static object FirstOrDefault(Type type, IEnumerable value)
Expression.Convert(methodCall, typeof(object)),
argument).Compile();
}

/// <summary>
/// Returns a cache key for the given method and type.
/// </summary>
/// <param name="type">
/// The <see cref="Type"/> the key reflects.
/// </param>
/// <param name="memberName">
/// The method name. Generated at compile time.
/// </param>
/// <returns>
/// The <see cref="MethodBaseCacheItem"/> for the given method and type.
/// </returns>
private static MethodBaseCacheItem GetMethodCacheKey(Type type, [CallerMemberName] string memberName = null)
{
return new MethodBaseCacheItem(memberName, type);
}
}
}
Loading