表达树的表现

我目前的理解是这样的’硬编码’代码:

public int Add(int x, int y) {return x + y;} 

将始终比表达式树代码执行更好,如下所示:

 Expression<Func> add = (x, y) => x + y; var result = add.Compile()(2, 3); var x = Expression.Parameter(typeof(int)); var y = Expression.Parameter(typeof(int)); return (Expression.Lambda(Expression.Add(x, y), x, y). Compile() as Func)(2, 3); 

因为编译器有更多的信息,如果你在编译时编译它,可以花更多的精力来优化代码。 这一般是正确的吗?

汇编

Expression.Compile的调用与您的应用程序包含的任何其他.NET代码完全相同,因为:

  • 生成IL代码
  • IL代码被JIT加载到机器代码

(跳过解析步骤,因为已经创建了表达式树,不必从输入代码生成)

您可以查看表达式编译器的源代码 ,以validation是否确实生成了IL代码。

优化

请注意,CLR完成的几乎所有优化都是在JIT步骤中完成的,而不是编译C#源代码。 在将lambda委托中的IL代码编译为机器代码时,也会进行此优化。

你的榜样

在您的示例中,您正在比较苹果和橙子。 第一个示例是方法定义,第二个示例是创建方法,编译和执行它的运行时代码。 创建/编译方法所花费的时间比实际执行它要长得多。 但是,您可以在创建后保留已编译方法的实例。 完成后,生成的方法的性能应与原始C#方法的性能相同。

考虑这种情况:

 private static int AddMethod(int a, int b) { return a + b; } Func add1 = (a, b) => a + b; Func add2 = AddMethod; var x = Expression.Parameter(typeof (int)); var y = Expression.Parameter(typeof (int)); var additionExpr = Expression.Add(x, y); Func add3 = Expression.Lambda>( additionExpr, x, y).Compile(); //the above steps cost a lot of time, relatively. //performance of these three should be identical add1(1, 2); add2(1, 2); add3(1, 2); 

因此,可能得出的结论是:IL代码是IL代码,无论它是如何生成的,Linq表达式生成IL代码。

您的Add函数可能会编译为某些函数开销(如果没有内联)和单个add指令。 没有比这更快。

即使构造这个表达式树也会慢一个数量级。 与直接C#实现相比,为每次调用编译一个新函数将是非常昂贵的。

尝试只编译一次该函数并将其存储在某处。

试图理解为什么我的构建和编译的lambda运行速度比“just delegate”慢一点(我想我需要为它创建新的SO问题)我找到了这个线程,并决定使用BenchmarkDotNet来检查性能。 给我带来惊喜:手动编译并编译lambda最快。 是的 – 方法之间存在稳定的差异。

结果:

 BenchmarkDotNet=v0.10.5, OS=Windows 10.0.14393 Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4 Frequency=3233539 Hz, Resolution=309.2587 ns, Timer=TSC [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0 Clr : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1648.0 Core : .NET Core 4.6.25009.03, 64bit RyuJIT Method | Job | Runtime | Mean | Error | StdDev | Median | Min | Max | Rank | Allocated | --------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|-----------:|-----:|----------:| AddBuilded | Clr | Clr | 0.8826 ns | 0.0278 ns | 0.0232 ns | 0.8913 ns | 0.8429 ns | 0.9195 ns | 1 | 0 B | AddLambda | Clr | Clr | 1.5077 ns | 0.0226 ns | 0.0212 ns | 1.4986 ns | 1.4769 ns | 1.5395 ns | 2 | 0 B | AddLambdaConst | Clr | Clr | 6.4535 ns | 0.0454 ns | 0.0425 ns | 6.4439 ns | 6.4030 ns | 6.5323 ns | 3 | 0 B | AddBuilded | Core | Core | 0.8993 ns | 0.0249 ns | 0.0233 ns | 0.8908 ns | 0.8777 ns | 0.9506 ns | 1 | 0 B | AddLambda | Core | Core | 1.5105 ns | 0.0241 ns | 0.0201 ns | 1.5094 ns | 1.4731 ns | 1.5396 ns | 2 | 0 B | AddLambdaConst | Core | Core | 9.3849 ns | 0.2237 ns | 0.5693 ns | 9.6577 ns | 8.3455 ns | 10.0590 ns | 4 | 0 B | 

我不能从中得出任何结论,它可能是IL代码或JIT编译器影响的差异。

码:

  static BenchmarkLambdaSimple() { addLambda = (a, b) => a + b; addLambdaConst = AddMethod; var x = Expression.Parameter(typeof(int)); var y = Expression.Parameter(typeof(int)); var additionExpr = Expression.Add(x, y); addBuilded = Expression.Lambda>( additionExpr, x, y).Compile(); } static Func addLambda; static Func addLambdaConst; static Func addBuilded; private static int AddMethod(int a, int b) { return a + b; } [Benchmark] public int AddBuilded() { return addBuilded(1, 2); } [Benchmark] public int AddLambda() { return addLambda(1, 2); } [Benchmark] public int AddLambdaConst() { return addLambdaConst(1, 2); } 

好吧,我已经写了一个小测试(可能需要你的专家仔细检查),但它看起来好像表达树是最快的(add3),然后是add2然后add1!

 using System; using System.Diagnostics; using System.Linq.Expressions; namespace ExpressionTreeTest { class Program { static void Main() { Func add1 = (a, b) => a + b; Func add2 = AddMethod; var x = Expression.Parameter(typeof(int)); var y = Expression.Parameter(typeof(int)); var additionExpr = Expression.Add(x, y); var add3 = Expression.Lambda>( additionExpr, x, y).Compile(); TimingTest(add1, "add1", 1000000); TimingTest(add2, "add2", 1000000); TimingTest(add3, "add3", 1000000); } private static void TimingTest(Func addMethod, string testMethod, int numberOfAdditions) { var sw = new Stopwatch(); sw.Start(); for (var c = 0; c < numberOfAdditions; c++) { addMethod(1, 2); } sw.Stop(); Console.WriteLine("Total calculation time {1}: {0}", sw.Elapsed, testMethod); } private static int AddMethod(int a, int b) { return a + b; } } } 

我的结果调试模式:

 Total calculation time add1: 00:00:00.0134612 Total calculation time add2: 00:00:00.0133916 Total calculation time add3: 00:00:00.0053629 

我的结果发布模式:

 Total calculation time add1: 00:00:00.0026172 Total calculation time add2: 00:00:00.0027046 Total calculation time add3: 00:00:00.0014334 

C#6.0现在允许您这样做:

 public int Add(int x, int y) => x + y; 

代替:

 public int Add(int x, int y) {return x + y;} 

请参见方法表达式和属性表达式