我目前的理解是'硬编码'像这样的代码:
public int Add(int x, int y) {return x + y;}
总是比表达式树代码执行得更好:
Expression<Func<int, int, int>> 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<int, int, int>)(2, 3);
因为编译器有更多信息,如果在编译时编译它,可以花更多精力优化代码。这一般是正确的吗?
答案 0 :(得分:20)
<强>汇编强>
对Expression.Compile
的调用与您的应用程序包含的任何其他.NET代码完全相同的过程:
(跳过解析步骤,因为已经创建了表达式树,不必从输入代码生成)
您可以查看表达式编译器的source code以验证是否确实生成了IL代码。
<强>优化强>
请注意,CLR完成的几乎所有优化都是在JIT步骤中完成的,而不是编译C#源代码。在将lambda委托中的IL代码编译为机器代码时,也会进行此优化。
您的示例
在你的例子中,你正在比较苹果和桔子。第一个示例是方法定义,第二个示例是创建方法,编译和执行它的运行时代码。创建/编译方法所花费的时间比实际执行它要长得多。但是,您可以在创建后保留已编译方法的实例。完成后,生成的方法的性能应与原始C#方法的性能相同。
考虑这种情况:
private static int AddMethod(int a, int b)
{
return a + b;
}
Func<int, int, int> add1 = (a, b) => a + b;
Func<int, int, int> add2 = AddMethod;
var x = Expression.Parameter(typeof (int));
var y = Expression.Parameter(typeof (int));
var additionExpr = Expression.Add(x, y);
Func<int, int, int> add3 =
Expression.Lambda<Func<int, int, int>>(
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代码。
答案 1 :(得分:5)
您的Add
函数可能编译为某些函数开销(如果没有内联)和单个add指令。没有比这快得多。
即使构建这个表达式树也会慢一个数量级。与直接C#实现相比,为每次调用编译一个新函数将非常昂贵。
尝试仅编译一次该函数并将其存储在某处。
答案 2 :(得分:3)
试图理解为什么我的构建和编译的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<Func<int, int, int>>(
additionExpr, x, y).Compile();
}
static Func<int, int, int> addLambda;
static Func<int, int, int> addLambdaConst;
static Func<int, int, int> 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);
}
答案 3 :(得分:2)
好的,我已经写了一些测试(可能需要你的专家仔细检查),但它看起来好像表达式树是最快的(add3),然后是add2然后是add1!
using System;
using System.Diagnostics;
using System.Linq.Expressions;
namespace ExpressionTreeTest
{
class Program
{
static void Main()
{
Func<int, int, int> add1 = (a, b) => a + b;
Func<int, int, int> add2 = AddMethod;
var x = Expression.Parameter(typeof(int));
var y = Expression.Parameter(typeof(int));
var additionExpr = Expression.Add(x, y);
var add3 = Expression.Lambda<Func<int, int, int>>(
additionExpr, x, y).Compile();
TimingTest(add1, "add1", 1000000);
TimingTest(add2, "add2", 1000000);
TimingTest(add3, "add3", 1000000);
}
private static void TimingTest(Func<int, int, int> 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
答案 4 :(得分:-4)
C#6.0现在允许你这样做:
public int Add(int x, int y) => x + y;
而不是:
public int Add(int x, int y) {return x + y;}
请参阅方法表达式和属性表达式