Optimizing Func.Invoke() generated from expression tree

c# expression-trees func performance

Question

I am working on an automation for instantiating classes dynamically.

I decided to write an expression tree that would generate a Func, that could instantiate my class for me. However, I am noticing 3x slower performance of my Func as opposed to simply using new.

From what I know about expression trees and invoking functions, the performance difference should be almost non-existant (maybe 20-30%, but nowhere near 3 times slower)

First off, here is the expression that I am building

public Expression<Func<A1, T>> BuildLambda<T, A1>(string param1Name)
    {
        var createdType = typeof(T);

        var param = Expression.Parameter(typeof(A1), param1Name);
        var ctor = Expression.New(createdType);
        var prop = createdType.GetProperty(param1Name);

        var displayValueAssignment = Expression.Bind(prop, param);
        var memberInit = Expression.MemberInit(ctor, displayValueAssignment);

        return
            Expression.Lambda<Func<A1, T>>(memberInit, param);
    }

I then proceed to compile it like so (I do this only once)

var c1 = mapper.BuildLambda<Class1, int>("Id").Compile();

And then I invoke my Func like so

var result = c1.Invoke(5);

When I put this last part in a loop and compare it to something like

var result = new Class1() { Id = 5 };

I did a couple of tests, comparing the performance in both, and this is what I ended up with:

100,000    Iterations - new: 0ms.   | Func 2ms.
600,000    Iterations - new: 5ms.   | Func 14ms.
3,100,000  Iterations - new: 24ms.  | Func 74ms.
15,600,000 Iterations - new: 118ms. | Func 378ms.
78,100,000 Iterations - new: 597ms. | Func 1767ms.

As you can see my Func.Invoke() is roughly 2.5 - 3 times slower than instantiating using new. Does anyone have any tips on how I might improve this? (I don't mind using pure reflection as I manage to get better performance)

*For anyone who wants to test this here is a pastebin of my setup: https://pastebin.com/yvMLqZ2t

Popular Answer

After reading through all the posts in the comments, I came up with this idea: when you create a DynamicMethod instead of an expression-tree and you assign it logically to the module of the current executing code, you should not get this overhead.

I think (or at least hope) that you were looking for improvement options on the general idea, not specifically the expression-tree based version, so I'm posting this as an improvement option :)

So I tried this piece of code:

 public static Func<A1, T> BuildLambda<A1, T>(string propertyName)
 {
   // This is where the magic happens with the last parameter!!
   DynamicMethod dm = new DynamicMethod("Create", typeof(T), new Type[] { typeof(A1) }, typeof(Program).Module);

   // Everything else is just generating IL-code at runtime to create the class and set the property
   var setter = typeof(T).GetProperty(propertyName).SetMethod;
   var generator = dm.GetILGenerator();
   var local = generator.DeclareLocal(typeof(T));
   generator.Emit(OpCodes.Newobj, typeof(Class1).GetConstructor(Type.EmptyTypes));
   generator.Emit(OpCodes.Stloc, local);
   generator.Emit(OpCodes.Ldloc, local);
   generator.Emit(OpCodes.Ldarg_0);
   generator.Emit(OpCodes.Call, setter);
   generator.Emit(OpCodes.Ldloc, local);
   generator.Emit(OpCodes.Ret);
   return (Func<A1, T>)dm.CreateDelegate(typeof(Func<A1, T>));
}

And on my machine this produced delegates that are executed max 1.8 times slower than the hand-written code, without specifying the attribute. Not 1.5, but at least I don't have to include an assembly-wide attribute to my code that I don't fully understand:)

Note that if you omit the last parameter of the DynamicMethod constructor, you still get the even slower results for the generated code.

EDIT

I stumbled upon this blog post, which poses the same question and gives the same solution:

https://blogs.msdn.microsoft.com/seteplia/2017/02/01/dissecting-the-new-constraint-in-c-a-perfect-example-of-a-leaky-abstraction/



Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why