Reputation: 493
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
Upvotes: 8
Views: 456
Reputation: 4350
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:
Upvotes: 2
Reputation: 4119
Let me try something different. The thing you might do is currying:
Func<TArg, TRes> BuildFuncFor<TClass, TArg, TRes>(Func<TClass> typeCreator, Action<TArg, TClass> argumentAssigner) {
return arg => {
var type = typeCreator();
argumentAssigner(arg, type);
return type;
}
}
Then, the same currying approach might be applied to supply both default/dynamic implementation of both func's. A typical typeCreator
would be something alike Activator.Create(...)
. Depending upon your logic, there might be more functions required; for example: Func<object[]> constructorArgumentsSupplier
. Same applies to the assigning a given value to a given property: good-old reflection: exactly the way WPF does.
But most of them 1) might be created only once for a certain type and cached for further usage; 2) precompiled rather than rely on expressions, which is kind of pain.
Upvotes: 0