问题描述
考虑以下对集合的简单操作:
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... Why is it creating a new delegate in every iteration? I'm not sure, but the solution follows in a separate post.
推荐答案
会不会是内部 lambdas 没有被编译?!?这是一个概念证明:
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 表达式性能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!