【问题标题】:Optimizing Func.Invoke() generated from expression tree优化从表达式树生成的 Func.Invoke()
【发布时间】:2018-01-04 16:30:49
【问题描述】:

我正在研究动态实例化类的自动化。

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

根据我对表达式树和调用函数的了解,性能差异应该几乎不存在(可能是 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

【问题讨论】:

  • 有什么理由不直接将其包含在问题中?删除未使用的ExpressionParam 类和命名空间声明后,只有大约 60 行...
  • 有趣的是,这只是完整的 .net 框架上的问题。 .Net Core 的行为符合预期。
  • 手动创建的表达式树的性能也与编译器从 lambda 表达式创建的表达式树的性能几乎相同。
  • 查看@IvanStoev 推荐的第一个问题的答案。如果您将[assembly: AllowPartiallyTrustedCallers] 添加到您的代码中,两个变体的结果几乎相同(在我的机器上最多慢1.5 倍的表达式调用)。此外,该答案中有一个指向another question 的链接,答案相同。不过,我不知道为什么,所以如果有人能比“这似乎是一个小错误”更深入,我也将不胜感激......

标签: c# performance expression-trees func


【解决方案1】:

在阅读了 cmets 中的所有帖子后,我想到了这个想法:当您创建 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.3 - 1.6 倍。我猜typeof(Program).Module 中的“魔力”基本上围绕着这样一个事实,即将该方法添加为当前程序集的一部分,这意味着没有其他程序集与之建立握手过程。 (这基本上是程序集属性帮助解决的问题)。感谢您的建议。如果我没有发现任何更有益的东西,我可能会使用它:)
  • 类似的东西。请注意,程序集是不可变的;一旦完成它就完成了,你不能在物理上添加任何东西。模块是组成程序集的更小的代码单元,如果你用 C# 编写你的 asm,你就看不到它们。这意味着您不能在编译后向模块添加任何内容。 docs 建议最后一个参数创建一个逻辑关联。因为它不是物理的,所以它没有添加到 asm 本身,只是从运行时(例如安全性)方面以某种方式链接到它,我猜。也许:)
【解决方案2】:

让我尝试一些不同的东西。你可能会做的是柯里化:

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

然后,可以应用相同的柯里化方法来提供两个 func 的默认/动态实现。典型的typeCreatorActivator.Create(...) 类似。根据您的逻辑,可能需要更多功能;例如:Func&lt;object[]&gt; constructorArgumentsSupplier。同样适用于为给定属性分配给定值:古老的反射:与 WPF 完全一样。

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

【讨论】:

    猜你喜欢
    • 2013-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多