LINQ中的聚合与总和性能

时间:2012-06-14 09:18:56

标签: c# performance linq sum aggregate

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

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

需要3毫秒

source.Sum(c => c);

需要12毫秒

source.Sum();

需要1毫秒

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

1 个答案:

答案 0 :(得分:78)

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

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

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

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

public static TAccumulate Aggregate<TSource, TAccumulate>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> 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<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
{
    return source.Select<TSource, int>(selector).Sum();
}

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

让我们验证使用Sum(lambda)实际上是使用Select()编写两次的原因,一次使用Sum(lambda),其行为应与框架中的Select()相同,并且一次不使用public static int SlowSum<T>(this IEnumerable<T> source, Func<T, int> selector) { return source.Select(selector).Sum(); } public static int FastSum<T>(this IEnumerable<T> source, Func<T, int> 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)

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

  • FastSum(lambda):12 ms
  • {{1}}:9 ms