Files
Umbraco-CMS/tests/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs.bak

248 lines
9.7 KiB
C#
Raw Normal View History

using System;
2017-07-25 13:33:32 +02:00
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Jobs;
using Perfolizer.Horology;
using Umbraco.Cms.Core;
2017-07-25 13:33:32 +02:00
namespace Umbraco.Tests.Benchmarks
{
// TODO: I very quickly tried converting this to netcore using the new AssemblyBuilder
// but didn't get far enough. I'm unsure where we use the different concepts of this code.
2018-11-27 13:32:31 +01:00
// some conclusions
// - ActivatorCreateInstance is slow
// - it's faster to get+invoke the ctor
// - emitting the ctor is unless if invoked only 1
2020-07-14 10:08:59 +10:00
// TODO: Check out https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.constructorbuilder?view=netcore-3.1 ?
2018-11-27 13:32:31 +01:00
//[Config(typeof(Config))]
[MemoryDiagnoser]
2017-07-25 13:33:32 +02:00
public class CtorInvokeBenchmarks
{
private class Config : ManualConfig
{
public Config()
{
Add(MemoryDiagnoser.Default);
2017-07-25 13:33:32 +02:00
//Add(ExecutionValidator.FailOnError);
//The 'quick and dirty' settings, so it runs a little quicker
// see benchmarkdotnet FAQ
Add(Job.Default
.WithLaunchCount(1) // benchmark process will be launched only once
2018-11-18 11:05:14 +01:00
.WithIterationTime(TimeInterval.FromMilliseconds(400))
2017-09-25 12:58:54 +02:00
.WithWarmupCount(3)
.WithIterationCount(6));
2017-07-25 13:33:32 +02:00
}
}
2017-09-25 12:58:54 +02:00
private readonly IFoo _foo = new Foo(null);
2017-07-25 13:33:32 +02:00
private ConstructorInfo _ctorInfo;
private Func<IFoo, IFoo> _dynamicMethod;
2017-09-25 12:58:54 +02:00
private Func<IFoo, IFoo> _expressionMethod;
private Func<IFoo, IFoo> _expressionMethod2;
private Func<IFoo, IFoo> _expressionMethod3;
private Func<IFoo, IFoo> _expressionMethod4;
2017-09-29 15:50:30 +02:00
private Func<IFoo, IFoo> _emittedCtor;
2017-07-25 13:33:32 +02:00
2018-05-16 10:06:51 +02:00
[GlobalSetup]
2017-07-25 13:33:32 +02:00
public void Setup()
{
var ctorArgTypes = new[] { typeof(IFoo) };
var type = typeof (Foo);
var constructor = _ctorInfo = type.GetConstructor(ctorArgTypes);
if (constructor == null)
throw new Exception("Failed to get the ctor.");
//IL_0000: ldarg.0 // this
//IL_0001: ldfld class Umbraco.Tests.Benchmarks.CtorInvokeBenchmarks/IFoo Umbraco.Tests.Benchmarks.CtorInvokeBenchmarks::_foo
//IL_0006: newobj instance void Umbraco.Tests.Benchmarks.CtorInvokeBenchmarks/Foo::.ctor(class Umbraco.Tests.Benchmarks.CtorInvokeBenchmarks/IFoo)
//IL_000b: pop
//IL_000c: ret
2017-09-25 12:58:54 +02:00
// generate a dynamic method
//
// ldarg.0 // obj0
// newobj instance void [Umbraco.Tests.Benchmarks]Umbraco.Tests.Benchmarks.CtorInvokeBenchmarks / Foo::.ctor(class [Umbraco.Tests.Benchmarks] Umbraco.Tests.Benchmarks.CtorInvokeBenchmarks/IFoo)
// ret
var meth = new DynamicMethod(string.Empty, typeof (IFoo), ctorArgTypes, type.Module, true);
2017-07-25 13:33:32 +02:00
var gen = meth.GetILGenerator();
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Newobj, constructor);
gen.Emit(OpCodes.Ret);
2017-09-25 12:58:54 +02:00
_dynamicMethod = (Func<IFoo, IFoo>) meth.CreateDelegate(typeof (Func<IFoo, IFoo>));
// generate a compiled expression
//
// ldarg.0 // content
// newobj instance void [Umbraco.Tests.Benchmarks]Umbraco.Tests.Benchmarks.CtorInvokeBenchmarks / Foo::.ctor(class [Umbraco.Tests.Benchmarks] Umbraco.Tests.Benchmarks.CtorInvokeBenchmarks/IFoo)
// ret
2017-07-25 13:33:32 +02:00
2017-09-25 12:58:54 +02:00
var exprArg = Expression.Parameter(typeof (IFoo), "content");
2017-07-25 13:33:32 +02:00
var exprNew = Expression.New(constructor, exprArg);
var expr = Expression.Lambda<Func<IFoo, IFoo>>(exprNew, exprArg);
2017-09-25 12:58:54 +02:00
_expressionMethod = expr.Compile();
// create a dynamic assembly
// dump to disk so we can review IL code with eg DotPeek
var assemblyName = new AssemblyName("Umbraco.Tests.Benchmarks.IL");
var assembly = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var module = assembly.DefineDynamicModule("CtorInvoke");
//var module = assembly.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll");
2017-09-25 12:58:54 +02:00
var typeBuilder = module.DefineType("CtorInvoke", TypeAttributes.Public | TypeAttributes.Abstract);
var expressionMethodBuilder = typeBuilder.DefineMethod("ExpressionCtor",
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
typeof (IFoo), ctorArgTypes);
2017-09-25 12:58:54 +02:00
expr.CompileToMethod(expressionMethodBuilder);
var dynamicMethodBuilder = typeBuilder.DefineMethod("DynamicCtor",
MethodAttributes.Public | MethodAttributes.Static,
typeof(IFoo), ctorArgTypes);
gen = dynamicMethodBuilder.GetILGenerator();
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Newobj, constructor);
gen.Emit(OpCodes.Ret);
meth.CreateDelegate(typeof (Func<IFoo, IFoo>));
var btype = typeBuilder.CreateType(); // need to build before saving
assembly.Save("Umbraco.Tests.Benchmarks.IL.dll");
// at that point,
// _dynamicMethod is 2x slower than direct ctor
// _expressionMethod is 6x slower than direct ctor
// which is weird as inspecting the assembly IL shows that they are generated
// exactly the same, and yet it is confirmed eg by https://stackoverflow.com/questions/4211418
//
// not sure why exactly
// see https://stackoverflow.com/questions/13431573
// see http://mattwarren.org/2017/01/25/How-do-.NET-delegates-work/#different-types-of-delegates
//
// note that all the benchmark methods have the very same IL code so it's
// really the 'callvirt ...' that ends up doing different things
//
// more readings:
// http://byterot.blogspot.dk/2012/05/performance-comparison-of-code.html
// https://stackoverflow.com/questions/1296683
// https://stackoverflow.com/questions/44239127
// that last one points to
// https://blogs.msdn.microsoft.com/seteplia/2017/02/01/dissecting-the-new-constraint-in-c-a-perfect-example-of-a-leaky-abstraction/
// which reads ... "Expression.Compile creates a DynamicMethod and associates it with an anonymous assembly
// to run it in a sandboxed environment. This makes it safe for a dynamic method to be emitted and executed
// by partially trusted code but adds some run-time overhead."
// and, turning things into a delegate (below) removes that overhead...
// turning it into a delegate seems cool, _expressionMethod2 is ~ _dynamicMethod
_expressionMethod2 = (Func<IFoo, IFoo>) Delegate.CreateDelegate(typeof (Func<IFoo, IFoo>), btype.GetMethod("ExpressionCtor"));
// nope, this won't work, throws an ArgumentException because 'MethodInfo must be a MethodInfo object'
// and here it's of type System.Reflection.Emit.DynamicMethod+RTDynamicMethod - whereas the btype one is ok
// so, the dynamic assembly step is required
//
//_expressionMethod3 = (Func<IFoo, IFoo>) Delegate.CreateDelegate(typeof (Func<IFoo, IFoo>), _expressionMethod.Method);
// but, our utilities know how to do it!
2019-12-06 12:09:47 +01:00
_expressionMethod3 = ReflectionUtilitiesForTest.CompileToDelegate(expr);
_expressionMethod4 = ReflectionUtilitiesForTest.GetCtor<Foo, IFoo>();
2017-09-27 21:16:09 +02:00
// however, unfortunately, the generated "compiled to delegate" code cannot access private stuff :(
2017-09-29 15:50:30 +02:00
2018-11-18 11:05:14 +01:00
_emittedCtor = ReflectionUtilities.EmitConstructor<Func<IFoo, Foo>>();
2017-07-25 13:33:32 +02:00
}
public IFoo IlCtor(IFoo foo)
{
return new Foo(foo);
}
2017-09-25 12:58:54 +02:00
[Benchmark(Baseline = true)]
2017-07-25 13:33:32 +02:00
public void DirectCtor()
{
var foo = new Foo(_foo);
}
[Benchmark]
2018-11-27 13:32:31 +01:00
public void EmitCtor()
{
var ctor = ReflectionUtilities.EmitConstructor<Func<IFoo, Foo>>();
2018-11-27 13:32:31 +01:00
var foo = ctor(_foo);
}
[Benchmark]
public void ActivatorCreateInstance()
{
var foo = Activator.CreateInstance(typeof(Foo), _foo);
}
[Benchmark]
public void GetAndInvokeCtor()
{
var ctorArgTypes = new[] { typeof(IFoo) };
var type = typeof(Foo);
var ctorInfo = type.GetConstructor(ctorArgTypes);
var foo = ctorInfo.Invoke(new object[] { _foo });
}
[Benchmark]
2017-07-25 13:33:32 +02:00
public void InvokeCtor()
{
var foo = _ctorInfo.Invoke(new object[] { _foo });
}
[Benchmark]
public void DynamicMethodCtor()
{
var foo = _dynamicMethod(_foo);
}
[Benchmark]
public void ExpressionCtor()
{
2017-09-25 12:58:54 +02:00
var foo = _expressionMethod(_foo);
}
[Benchmark]
public void Expression2Ctor()
{
var foo = _expressionMethod2(_foo);
}
[Benchmark]
public void Expression3Ctor()
{
var foo = _expressionMethod3(_foo);
}
[Benchmark]
public void Expression4Ctor()
{
var foo = _expressionMethod4(_foo);
2017-07-25 13:33:32 +02:00
}
2017-09-29 15:50:30 +02:00
[Benchmark]
public void EmittedCtor()
{
var foo = _emittedCtor(_foo);
}
2017-07-25 13:33:32 +02:00
public interface IFoo
{ }
public class Foo : IFoo
{
public Foo(IFoo foo)
{ }
}
}
}