C# LINQ中的聚合与求和性能

C# LINQ中的聚合与求和性能,c#,performance,linq,sum,aggregate,C#,Performance,Linq,Sum,Aggregate,下面给出了查找IEnumerable源和的三种不同实现,以及源有10000个整数时所花费的时间 source.Aggregate(0, (result, element) => result + element); 需要3毫秒 source.Sum(c => c); 需要12毫秒 source.Sum(); 需要1毫秒 我想知道为什么第二个实现比第一个要贵四倍。它不应该与第三个实现相同。注意:我的计算机正在运行.Net 4.5 RC,因此我的结果可能会受到影响 仅测量一次执

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

source.Aggregate(0, (result, element) => result + element);  
需要3毫秒

source.Sum(c => c);
需要12毫秒

source.Sum();
需要1毫秒


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

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

仅测量一次执行方法所需的时间通常不是很有用。它很容易被JIT编译之类的东西所控制,而JIT编译在实际代码中并不是真正的瓶颈。因此,我对每个方法的执行情况进行了100×(在没有附加调试程序的发布模式下)的测量。我的结果是:

  • Aggregate()
    :9毫秒
  • 总和(λ)
    :12毫秒
  • Sum()
    :6毫秒
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();
}
我的测量结果证实了我的想法:

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

    • 才晚了十年,但是

      我曾经做过一个Linq替换(,),它(大部分!)是System.Linq的替代品(只需添加nuget包,然后使用System.Linq将
      更改为
      使用Cystern.ValueLinq
      ),但是使用了值类型和一些隐藏的技巧

      无论如何,只要
      Sum
      ,它就可以在后台使用SIMD指令(仍然支持溢出)

      下面的结果运行在一台机器上,这台机器和原来的stackoverflow问题一样古老,因此现代机器应该会有更好的结果(尽管对于不支持SIMD的IEnumerable,您确实会得到一些开销)

      公共枚举容器类型{可枚举,数组,列表,}
      [记忆诊断器]
      公共课基准
      {
      IEnumerable_数据;
      [参数(ContainerTypes.Array、ContainerTypes.Enumerable、ContainerTypes.List)]
      public ContainerTypes ContainerType{get;set;}=ContainerTypes.Enumerable;
      [全球设置]
      公共数据()
      {
      var数据=System.Linq.Enumerable.Range(0,1000);
      _数据=容器类型开关
      {
      ContainerTypes.Enumerable=>data,
      ContainerTypes.Array=>System.Linq.Enumerable.ToArray(数据),
      ContainerTypes.List=>System.Linq.Enumerable.ToList(数据),
      _=>抛出新异常(“未知容器类型”)
      };
      }
      [Benchmark(Baseline=true)]public int System_Linq_Sum()=>System.Linq.Enumerable.Sum(_data);
      [Benchmark]public int System_Linq_Sum_Predicate()=>System.Linq.Enumerable.Sum(_data,c=>c);
      [Benchmark]public int System_Linq_Aggregate()=>System.Linq.Enumerable.Aggregate(_data,0,(result,element)=>result+element);
      [Benchmark]public int-Cistern_-ValueLinq_-Sum()=>Cistern.ValueLinq.Enumerable.Sum(_-data);
      [Benchmark]public int-Cistern\u-ValueLinq\u-Sum\u谓词()=>Cistern.ValueLinq.Enumerable.Sum(\u-data,c=>c);
      [Benchmark]public int-Cistern\u-ValueLinq\u-Aggregate()=>Cistern.ValueLinq.Enumerable.Aggregate(\u-data,0,(result,element)=>result+element);
      静态void Main(字符串[]args)=>BenchmarkRunner.Run();
      }
      
      方法 容器类型 卑鄙 错误 标准偏差 中值的 比率 比率 第0代 第1代 第2代 分配 系统线性求和 可枚举 6.025美元 0.1155美元 0.1501美元 6.041美国 1 0 0.0076 - - 40 B 系统Linq和谓词 可枚举 8.731美元 0.1727美元 0.3681美元 8.742美元 1.45 0.08 - - - 40 B 系统Linq集合 可枚举 8.534美元 0.1683美元 0.3514美元 8.657美元 1.42 0.07 - - - 40 B 蓄水池数值和 可枚举 6.907美国 0.1369美元 0.3061美元 6.911美国 1.15 0.07 0.0076 - - 40 B 蓄水池值和谓词 可枚举 9.769美元 0.1907美国 0.1784美元 9.823美元 1.62 0.04 - - - 40 B 蓄水池值集料 可枚举 9.295美元 0.1847美元 0.4962美元 9.396美元 1.52 0.08 - - - 40 B 系统线性求和 排列 6.731美国 0.1343美国 0.3653美元 6.814美国 1 0 0.0076 - - 32 B 系统Linq和谓词 排列 9.432美国 0.1873美元 0.5065美元 9.685美元 1.41 0.11 - - - 32 B 系统Linq集合 排列 9.494美元 0.1890美国 0.4707美元 9.759美元 1.41 0.11 - - - 32 B 蓄水池数值和 排列 1.404美国 0.0279美元 0.0710美元 1.436美元 0.21 0.02 - - - - 蓄水池值和谓词 排列 4.064美国 0.0811美元 0.0996美国 4.087美元 0.61 0.04 - - - - 蓄水池值集料 排列 3.549美元 0.0709美元 0.1496美元 3.584美元 0.53 0.04 - - - - 系统线性求和 列表 11.779美元 0.2344美元 0.3048美元 11.854美国 1 0 - - - 40 B 系统Linq和谓词 列表 14.227美元 0.2842美元 0.6919美元 14.601美国 1.22 0.05 - - - 40 B 系统Linq集合 列表 13.852美元 0.2761美元 0.7418美元 14.249美国 1.17 0.06 - - - 40 B 蓄水池数值和 列表 1.437美元 0.0288美元 0.0835美元 1.471美国 0.12 0.01 - - - - 蓄水池值和谓词 列表 3.672美国 0.0732美元 0.1941美国 3.771美元 0.32 0.02 - - - - 蓄水池值集料 列表 3.597美元 0.0718美元 0.1880美元 3.698美元 0.31 0.02 - - - -
      你的测试条件是什么?你是怎么得到这些时间的?你尝试了多少次结果?我用dotTrace分析了它。我运行过一次,但三次运行是独立的。尝试在一个循环中运行每个测试,这将删除所有用于jitting等的时间,测试将更准确。运行两次后结果仍然相同。非常有见地。感谢您的详细回复。这是我在《SO》上看到的最好的答案之一,解释得很好,制作得很好。(+1)foreach也会创建一个迭代器,所以我不认为这个解释是正确的。@kjbartel我相信什么
      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;
      }