LINQ中的聚合与总和性能

下面给出了三种不同的实现,即查找IEnumerable 源的总和,以及源具有10,000个整数所花费的时间。

source.Aggregate(0, (result, element) => result + element); 

需要3毫秒

 source.Sum(c => c); 

需要12毫秒

 source.Sum(); 

需要1毫秒

我想知道为什么第二次实施比第一次实施贵四倍。 不应该与第三个实现相同。

注意:我的计算机正在运行.Net 4.5 RC,因此我的结果可能会受此影响。

测量一次执行方法所花费的时间通常不是很有用。 它很容易被像JIT编译这样的东西所支配,而这些东西并不是实际代码中的实际瓶颈。 因此,我测量了每个方法执行100×(在没有附带调试器的发布模式下)。 我的结果是:

  • Aggregate() :9毫秒
  • Sum(lambda) :12毫秒
  • Sum() :6 ms

Sum()最快的事实并不令人惊讶:它包含一个没有任何委托调用的简单循环,这非常快。 Sum(lambda)Aggregate()之间的差异并不像你测量的那么显着,但它仍然存在。 可能是什么原因呢? 让我们看看这两种方法的反编译代码:

 public static TAccumulate Aggregate(this IEnumerable source, TAccumulate seed, Func func) { if (source == null) throw Error.ArgumentNull("source"); if (func == null) throw Error.ArgumentNull("func"); TAccumulate local = seed; foreach (TSource local2 in source) local = func(local, local2); return local; } public static int Sum(this IEnumerable source, Func selector) { return source.Select(selector).Sum(); } 

如您所见, Aggregate()使用循环但Sum(lambda)使用Select() ,而Select()又使用迭代器。 使用迭代器意味着有一些开销:创建迭代器对象,并且(可能更重要的是)为每个项创建一个方法调用。

让我们validation使用Select()实际上是通过两次编写我们自己的Sum(lambda)的原因,一次使用Select() ,它应该与框架中的Sum(lambda)相同,并且一次不使用Select()

 public static int SlowSum(this IEnumerable source, Func selector) { return source.Select(selector).Sum(); } public static int FastSum(this IEnumerable source, Func selector) { if (source == null) throw new ArgumentNullException("source"); if (selector == null) throw new ArgumentNullException("selector"); int num = 0; foreach (T item in source) num += selector(item); return num; } 

我的测量证实了我的想法:

  • SlowSum(lambda) :12毫秒
  • FastSum(lambda) :9 ms