优化从表达式树生成的Func.Invoke()

时间:2018-01-04 16:30:49

标签: c# performance expression-trees func

我正致力于动态实例化类的自动化。

我决定编写一个生成Func的表达式树,可以为我实例化我的类。但是,我注意到Func的性能降低了3倍,而不是简单地使用new

根据我对表达树和调用函数的了解,性能差异应该几乎不存在(可能是20-30%,但远不及慢3倍)

首先,这是我正在构建的表达

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);
    }

然后我继续编译它(我只做一次)

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

然后我像这样调用我的Func

var result = c1.Invoke(5);

当我把这最后一部分放在循环中并将其与

之类的东西进行比较时
var result = new Class1() { Id = 5 };

我做了几个测试,比较两者的性能,这就是我最终的结果:

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.

正如您所看到的,Func.Invoke()与使用new进行实例化相比,大约 2.5 - 3倍。 有没有人有任何关于如何改进这个的提示? (我不介意使用纯粹的反射,因为我设法获得更好的性能)

*对于想要在此处测试的人,我的设置为https://pastebin.com/yvMLqZ2t

2 个答案:

答案 0 :(得分:2)

在阅读了评论中的所有帖子后,我想出了这个想法:当你创建一个DynamicMethod而不是表达式树并且你将它逻辑地分配给当前执行代码的模块时,你不应该得到这个开销。

我认为(或至少希望)您正在寻找关于一般概念的改进选项,而不是基于表达式树的版本,因此我将其作为改进选项发布:)

所以我尝试了这段代码:

 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>));
}

在我的机器上,这些代表的执行速度比手写代码慢了1.8倍,没有指定属性。不是1.5,但至少我不必在我的代码中包含我不完全理解的程序集范围的属性:)

请注意,如果省略DynamicMethod构造函数的最后一个参数,则生成的代码仍会得到更慢的结果。

修改

我偶然发现了这篇博文,它提出了同样的问题并给出了相同的解决方案:

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

答案 1 :(得分:0)

让我尝试不同的东西。你可能会做的就是讨好:

Func<TArg, TRes> BuildFuncFor<TClass, TArg, TRes>(Func<TClass> typeCreator, Action<TArg, TClass> argumentAssigner) {
        return arg => {
             var type = typeCreator();
             argumentAssigner(arg, type);
             return type;
    }
}

然后,可以应用相同的currying方法来提供两个func的默认/动态实现。典型的typeCreator类似于Activator.Create(...)。根据您的逻辑,可能需要更多功能;例如:Func<object[]> constructorArgumentsSupplier。同样适用于将给定值分配给给定属性:良好的旧反射:完全符合WPF的方式。

但是大多数1)可能只为某种类型创建一次并缓存以供进一步使用; 2)预编译而不是依赖表达式,这是一种痛苦。