using System; 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; 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. // some conclusions // - ActivatorCreateInstance is slow // - it's faster to get+invoke the ctor // - emitting the ctor is unless if invoked only 1 // TODO: Check out https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.constructorbuilder?view=netcore-3.1 ? //[Config(typeof(Config))] [MemoryDiagnoser] public class CtorInvokeBenchmarks { private class Config : ManualConfig { public Config() { Add(MemoryDiagnoser.Default); //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 .WithIterationTime(TimeInterval.FromMilliseconds(400)) .WithWarmupCount(3) .WithIterationCount(6)); } } private readonly IFoo _foo = new Foo(null); private ConstructorInfo _ctorInfo; private Func _dynamicMethod; private Func _expressionMethod; private Func _expressionMethod2; private Func _expressionMethod3; private Func _expressionMethod4; private Func _emittedCtor; [GlobalSetup] 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 // 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); var gen = meth.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Newobj, constructor); gen.Emit(OpCodes.Ret); _dynamicMethod = (Func) meth.CreateDelegate(typeof (Func)); // 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 var exprArg = Expression.Parameter(typeof (IFoo), "content"); var exprNew = Expression.New(constructor, exprArg); var expr = Expression.Lambda>(exprNew, exprArg); _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"); 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); 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)); 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) Delegate.CreateDelegate(typeof (Func), 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) Delegate.CreateDelegate(typeof (Func), _expressionMethod.Method); // but, our utilities know how to do it! _expressionMethod3 = ReflectionUtilitiesForTest.CompileToDelegate(expr); _expressionMethod4 = ReflectionUtilitiesForTest.GetCtor(); // however, unfortunately, the generated "compiled to delegate" code cannot access private stuff :( _emittedCtor = ReflectionUtilities.EmitConstructor>(); } public IFoo IlCtor(IFoo foo) { return new Foo(foo); } [Benchmark(Baseline = true)] public void DirectCtor() { var foo = new Foo(_foo); } [Benchmark] public void EmitCtor() { var ctor = ReflectionUtilities.EmitConstructor>(); 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] public void InvokeCtor() { var foo = _ctorInfo.Invoke(new object[] { _foo }); } [Benchmark] public void DynamicMethodCtor() { var foo = _dynamicMethod(_foo); } [Benchmark] public void ExpressionCtor() { 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); } [Benchmark] public void EmittedCtor() { var foo = _emittedCtor(_foo); } public interface IFoo { } public class Foo : IFoo { public Foo(IFoo foo) { } } } }