C# LINQ中的聚合与求和性能
下面给出了查找IEnumerableC# 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,因此我的结果可能会受到影响 仅测量一次执
source.Aggregate(0, (result, element) => result + element);
需要3毫秒
source.Sum(c => c);
需要12毫秒
source.Sum();
需要1毫秒
我想知道为什么第二个实现比第一个要贵四倍。它不应该与第三个实现相同。注意:我的计算机正在运行.Net 4.5 RC,因此我的结果可能会受到影响 仅测量一次执行方法所需的时间通常不是很有用。它很容易被JIT编译之类的东西所控制,而JIT编译在实际代码中并不是真正的瓶颈。因此,我对每个方法的执行情况进行了100×(在没有附加调试程序的发布模式下)的测量。我的结果是:
:9毫秒Aggregate()
:12毫秒总和(λ)
:6毫秒Sum()
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();
}
我的测量结果证实了我的想法:
:12毫秒SlowSum(lambda)
:9毫秒FastSum(lambda)
- 才晚了十年,但是
我曾经做过一个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;
}