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