编译的C#Lambda表达式性能 [英] Compiled C# Lambda Expressions Performance
问题描述
请考虑对集合进行以下简单操作:
Consider the following simple manipulation over a collection:
static List<int> x = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var result = x.Where(i => i % 2 == 0).Where(i => i > 5);
现在让我们使用表达式.以下代码大致等效:
Now let's use Expressions. The following code is roughly equivalent:
static void UsingLambda() {
Func<IEnumerable<int>, IEnumerable<int>> lambda = l => l.Where(i => i % 2 == 0).Where(i => i > 5);
var t0 = DateTime.Now.Ticks;
for (int j = 1; j < MAX; j++)
var sss = lambda(x).ToList();
var tn = DateTime.Now.Ticks;
Console.WriteLine("Using lambda: {0}", tn - t0);
}
但是我想即时构建表达式,所以这是一个新的测试:
But I want to build the expression on-the-fly, so here's a new test:
static void UsingCompiledExpression() {
var f1 = (Expression<Func<IEnumerable<int>, IEnumerable<int>>>)(l => l.Where(i => i % 2 == 0));
var f2 = (Expression<Func<IEnumerable<int>, IEnumerable<int>>>)(l => l.Where(i => i > 5));
var argX = Expression.Parameter(typeof(IEnumerable<int>), "x");
var f3 = Expression.Invoke(f2, Expression.Invoke(f1, argX));
var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(f3, argX);
var c3 = f.Compile();
var t0 = DateTime.Now.Ticks;
for (int j = 1; j < MAX; j++)
var sss = c3(x).ToList();
var tn = DateTime.Now.Ticks;
Console.WriteLine("Using lambda compiled: {0}", tn - t0);
}
当然,它与上面的不完全一样,因此,为了公平起见,我对第一个进行了一些修改:
Of course it isn't exactly like the above, so to be fair, I modify the first one slightly:
static void UsingLambdaCombined() {
Func<IEnumerable<int>, IEnumerable<int>> f1 = l => l.Where(i => i % 2 == 0);
Func<IEnumerable<int>, IEnumerable<int>> f2 = l => l.Where(i => i > 5);
Func<IEnumerable<int>, IEnumerable<int>> lambdaCombined = l => f2(f1(l));
var t0 = DateTime.Now.Ticks;
for (int j = 1; j < MAX; j++)
var sss = lambdaCombined(x).ToList();
var tn = DateTime.Now.Ticks;
Console.WriteLine("Using lambda combined: {0}", tn - t0);
}
现在得出MAX = 100000,VS2008,调试打开的结果:
Now comes the results for MAX = 100000, VS2008, debugging ON:
Using lambda compiled: 23437500
Using lambda: 1250000
Using lambda combined: 1406250
并关闭调试:
Using lambda compiled: 21718750
Using lambda: 937500
Using lambda combined: 1093750
惊奇.编译后的表达式比其他表达式慢大约17倍.现在出现了问题:
Surprise. The compiled expression is roughly 17x slower than the other alternatives. Now here comes the questions:
- 我在比较非对等表达式吗?
- 是否存在使.NET优化"编译表达式的机制?
- 如何以编程方式表示相同的链调用
l.Where(i => i % 2 == 0).Where(i => i > 5);
?
更多统计信息. Visual Studio 2010,调试打开,优化关闭:
Some more statistics. Visual Studio 2010, debugging ON, optimizations OFF:
Using lambda: 1093974
Using lambda compiled: 15315636
Using lambda combined: 781410
调试打开,优化打开:
Using lambda: 781305
Using lambda compiled: 15469839
Using lambda combined: 468783
关闭调试,打开优化:
Using lambda: 625020
Using lambda compiled: 14687970
Using lambda combined: 468765
新惊喜.从VS2008(C#3)切换到VS2010(C#4),使UsingLambdaCombined
比本地lambda更快.
New Surprise. Switching from VS2008 (C#3) to VS2010 (C#4), makes the UsingLambdaCombined
faster than the native lambda.
好吧,我找到了一种将lambda编译性能提高一个数量级以上的方法.这是一个提示;运行探查器后,有92%的时间用于:
Ok, I've found a way to improve the lambda compiled performance by more than an order of magnitude. Here's a tip; after running the profiler, 92% of the time is spent on:
System.Reflection.Emit.DynamicMethod.CreateDelegate(class System.Type, object)
Hmmmm ...为什么要在每次迭代中创建一个新的委托?我不确定,但是解决方案会在另一篇文章中介绍.
Hmmmm... Why is it creating a new delegate in every iteration? I'm not sure, but the solution follows in a separate post.
推荐答案
是不是内部lambda尚未编译?!?这是概念证明:
Could it be that the inner lambdas are not being compiled?!? Here's a proof of concept:
static void UsingCompiledExpressionWithMethodCall() {
var where = typeof(Enumerable).GetMember("Where").First() as System.Reflection.MethodInfo;
where = where.MakeGenericMethod(typeof(int));
var l = Expression.Parameter(typeof(IEnumerable<int>), "l");
var arg0 = Expression.Parameter(typeof(int), "i");
var lambda0 = Expression.Lambda<Func<int, bool>>(
Expression.Equal(Expression.Modulo(arg0, Expression.Constant(2)),
Expression.Constant(0)), arg0).Compile();
var c1 = Expression.Call(where, l, Expression.Constant(lambda0));
var arg1 = Expression.Parameter(typeof(int), "i");
var lambda1 = Expression.Lambda<Func<int, bool>>(Expression.GreaterThan(arg1, Expression.Constant(5)), arg1).Compile();
var c2 = Expression.Call(where, c1, Expression.Constant(lambda1));
var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(c2, l);
var c3 = f.Compile();
var t0 = DateTime.Now.Ticks;
for (int j = 1; j < MAX; j++)
{
var sss = c3(x).ToList();
}
var tn = DateTime.Now.Ticks;
Console.WriteLine("Using lambda compiled with MethodCall: {0}", tn - t0);
}
现在计时是:
Using lambda: 625020
Using lambda compiled: 14687970
Using lambda combined: 468765
Using lambda compiled with MethodCall: 468765
哇!它不仅速度快,而且比本地lambda更快. (抓头).
Woot! Not only it is fast, it is faster than the native lambda. (Scratch head).
当然,上面的代码写起来实在太痛苦了.让我们做一些简单的魔术:
Of course the above code is simply too painful to write. Let's do some simple magic:
static void UsingCompiledConstantExpressions() {
var f1 = (Func<IEnumerable<int>, IEnumerable<int>>)(l => l.Where(i => i % 2 == 0));
var f2 = (Func<IEnumerable<int>, IEnumerable<int>>)(l => l.Where(i => i > 5));
var argX = Expression.Parameter(typeof(IEnumerable<int>), "x");
var f3 = Expression.Invoke(Expression.Constant(f2), Expression.Invoke(Expression.Constant(f1), argX));
var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(f3, argX);
var c3 = f.Compile();
var t0 = DateTime.Now.Ticks;
for (int j = 1; j < MAX; j++) {
var sss = c3(x).ToList();
}
var tn = DateTime.Now.Ticks;
Console.WriteLine("Using lambda compiled constant: {0}", tn - t0);
}
以及一些时间安排,VS2010,优化打开,调试关闭:
And some timings, VS2010, Optimizations ON, Debugging OFF:
Using lambda: 781260
Using lambda compiled: 14687970
Using lambda combined: 468756
Using lambda compiled with MethodCall: 468756
Using lambda compiled constant: 468756
现在您可能会说我不是动态生成整个表达式;而是只是链接调用.但是在上面的示例中,我生成了整个表达式.和时间匹配.这只是编写更少代码的捷径.
Now you could argue that I'm not generating the whole expression dynamically; just the chaining invocations. But in the above example I generate the whole expression. And the timings match. This is just a shortcut to write less code.
据我所知,正在发生的事情是.Compile()方法不会将编译传播到内部lambda,因此会不断调用CreateDelegate
.但是要真正理解这一点,我很乐意让.NET专家对正在进行的内部工作发表一些评论.
From my understanding, what is going on is that the .Compile() method does not propagate the compilations to inner lambdas, and thus the constant invocation of CreateDelegate
. But to truly understand this, I would love to have a .NET guru comment a little about the internal stuff going on.
为什么为什么,哦为什么现在比本地lambda快!?
And why, oh why is this now faster than a native lambda!?
这篇关于编译的C#Lambda表达式性能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!