【发布时间】:2011-07-30 21:51:47
【问题描述】:
考虑以下对集合的简单操作:
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);
现在让我们使用表达式。下面的代码大致等价:
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);
}
但我想即时构建表达式,所以这里有一个新测试:
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);
}
当然和上面的不完全一样,所以为了公平起见,我稍微修改了第一个:
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,调试 ON 的结果:
Using lambda compiled: 23437500
Using lambda: 1250000
Using lambda combined: 1406250
调试关闭:
Using lambda compiled: 21718750
Using lambda: 937500
Using lambda combined: 1093750
惊喜。编译后的表达式比其他替代方法慢大约 17 倍。现在问题来了:
- 我是在比较不等价的表达式吗?
- 是否有一种机制可以让 .NET “优化”编译后的表达式?
- 如何以编程方式表达相同的链调用
l.Where(i => i % 2 == 0).Where(i => i > 5);?
更多统计数据。 Visual Studio 2010,调试开启,优化关闭:
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 更快。
好的,我找到了一种方法,可以将 lambda 编译的性能提高一个数量级以上。这是一个提示;运行分析器后,92% 的时间花在:
System.Reflection.Emit.DynamicMethod.CreateDelegate(class System.Type, object)
嗯……为什么每次迭代都创建一个新的委托?我不确定,但解决方案在单独的帖子中。
【问题讨论】:
-
这些时间是在 Visual Studio 中运行的吗?如果是这样,请使用发布模式构建重复计时并在不调试的情况下运行(即在 Visual Studio 中或从命令行中使用 Ctrl+F5)。另外,请考虑使用
Stopwatch而非DateTime.Now进行计时。 -
我不知道为什么它比较慢,但是你的基准测试技术不是很好。首先, DateTime.Now 仅精确到 1/64 秒,因此您的测量舍入误差很大。改用秒表;它精确到几纳秒。其次,您正在测量 jit 代码(第一次调用)和每个后续调用的时间;这可以甩掉平均值。 (尽管在这种情况下,十万的 MAX 可能足以平均化 jit 负担,但将其包含在平均值中仍然是一种不好的做法。)
-
@Eric,只有在每个操作中使用 DateTime.Now.Ticks 时才会出现舍入误差,在开始之前和结束之后,毫秒计数足够高以显示性能差异。
-
如果使用秒表,我建议阅读这篇文章以确保准确的结果:codeproject.com/KB/testing/stopwatch-measure-precise.aspx
-
@Eric,虽然我同意这不是最精确的测量技术,但我们谈论的是一个数量级的差异。 MAX 足够高,可以减少明显的偏差。
标签: c# performance lambda expression-trees