下面给出了查找IEnumerable <int> 源的总和的三种不同实现,以及源具有 10,000 个整数时所用的时间。
source.Aggregate(0, (result, element) => result + element);
需要 3 毫秒
source.Sum(c => c);
需要 12 毫秒
source.Sum();
需要 1 毫秒
我想知道为什么第二个实现比第一个贵四倍。它不应该与第三个实现相同。
下面给出了查找IEnumerable <int> 源的总和的三种不同实现,以及源具有 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 毫秒最快的事实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()
,而后者又使用迭代器。使用迭代器意味着有一些开销:创建迭代器对象和(可能更重要的是)为每个项目再调用一次方法。
让我们通过编写我们自己的两次来验证 usingSelect()
实际上是原因,一次 using ,其行为应该与框架中的行为相同,一次不使用 using :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)
: 12 毫秒FastSum(lambda)
: 9 毫秒仅仅晚了十年,但...
我已经研究过 Linq 替换(nuget,github),它(主要是!)替代 System.Linq (只需添加 nuget 包,然后更改using System.Linq
为using Cistern.ValueLinq
),但使用值类型和一些技巧.
无论如何,就目前Sum
而言,它在可能的地方使用了 SIMD 指令(仍然尊重溢出)。
下面的结果运行在与原始 stackoverflow 问题一样旧的机器上,因此现代机器甚至应该有更好的结果(尽管对于非 SIMD 能够 IEnumerable,您确实会获得一些开销)
public enum ContainerTypes { Enumerable, Array, List, }
[MemoryDiagnoser]
public class Benchmarks
{
IEnumerable<int> _data;
[Params(ContainerTypes.Array, ContainerTypes.Enumerable, ContainerTypes.List)]
public ContainerTypes ContainerType { get; set; } = ContainerTypes.Enumerable;
[GlobalSetup]
public void SetupData()
{
var data = System.Linq.Enumerable.Range(0, 1000);
_data = ContainerType switch
{
ContainerTypes.Enumerable => data,
ContainerTypes.Array => System.Linq.Enumerable.ToArray(data),
ContainerTypes.List => System.Linq.Enumerable.ToList(data),
_ => throw new Exception("Unknown ContainerType")
};
}
[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_ValueLinq_Sum_Predicate() => Cistern.ValueLinq.Enumerable.Sum(_data, c => c);
[Benchmark] public int Cistern_ValueLinq_Aggregate() => Cistern.ValueLinq.Enumerable.Aggregate(_data, 0, (result, element) => result + element);
static void Main(string[] args) => BenchmarkRunner.Run<Benchmarks>();
}
方法 | 容器类型 | 意思是 | 错误 | 标准差 | 中位数 | 比率 | 比率标准差 | 0代 | 第一代 | 第 2 代 | 已分配 |
---|---|---|---|---|---|---|---|---|---|---|---|
System_Linq_Sum | 可枚举 | 6.025 我们 | 0.1155 我们 | 0.1501 我们 | 6.041 我们 | 1.00 | 0.00 | 0.0076 | - | - | 40乙 |
System_Linq_Sum_Predicate | 可枚举 | 8.731 我们 | 0.1727 我们 | 0.3681 我们 | 8.742 我们 | 1.45 | 0.08 | - | - | - | 40乙 |
System_Linq_Aggregate | 可枚举 | 8.534 我们 | 0.1683 我们 | 0.3514 我们 | 8.657 我们 | 1.42 | 0.07 | - | - | - | 40乙 |
Cistern_ValueLinq_Sum | 可枚举 | 6.907 我们 | 0.1369 我们 | 0.3061 我们 | 6.911 我们 | 1.15 | 0.07 | 0.0076 | - | - | 40乙 |
Cistern_ValueLinq_Sum_Predicate | 可枚举 | 9.769 我们 | 0.1907 我们 | 0.1784 我们 | 9.823 我们 | 1.62 | 0.04 | - | - | - | 40乙 |
Cistern_ValueLinq_Aggregate | 可枚举 | 9.295 我们 | 0.1847 我们 | 0.4962 我们 | 9.396 我们 | 1.52 | 0.08 | - | - | - | 40乙 |
System_Linq_Sum | 大批 | 6.731 我们 | 0.1343 我们 | 0.3653 我们 | 6.814 我们 | 1.00 | 0.00 | 0.0076 | - | - | 32乙 |
System_Linq_Sum_Predicate | 大批 | 9.432 我们 | 0.1873 我们 | 0.5065 我们 | 9.685 我们 | 1.41 | 0.11 | - | - | - | 32乙 |
System_Linq_Aggregate | 大批 | 9.494 我们 | 0.1890 我们 | 0.4707 我们 | 9.759 我们 | 1.41 | 0.11 | - | - | - | 32乙 |
Cistern_ValueLinq_Sum | 大批 | 1.404 我们 | 0.0279 我们 | 0.0710 我们 | 1.436 我们 | 0.21 | 0.02 | - | - | - | - |
Cistern_ValueLinq_Sum_Predicate | 大批 | 4.064 我们 | 0.0811 我们 | 0.0996 我们 | 4.087 我们 | 0.61 | 0.04 | - | - | - | - |
Cistern_ValueLinq_Aggregate | 大批 | 3.549 我们 | 0.0709 我们 | 0.1496 我们 | 3.584 我们 | 0.53 | 0.04 | - | - | - | - |
System_Linq_Sum | 列表 | 11.779 我们 | 0.2344 我们 | 0.3048 我们 | 11.854 我们 | 1.00 | 0.00 | - | - | - | 40乙 |
System_Linq_Sum_Predicate | 列表 | 14.227 我们 | 0.2842 我们 | 0.6919 我们 | 14.601 我们 | 1.22 | 0.05 | - | - | - | 40乙 |
System_Linq_Aggregate | 列表 | 13.852 我们 | 0.2761 我们 | 0.7418 我们 | 14.249 我们 | 1.17 | 0.06 | - | - | - | 40乙 |
Cistern_ValueLinq_Sum | 列表 | 1.437 我们 | 0.0288 我们 | 0.0835 我们 | 1.471 我们 | 0.12 | 0.01 | - | - | - | - |
Cistern_ValueLinq_Sum_Predicate | 列表 | 3.672 我们 | 0.0732 我们 | 0.1941 我们 | 3.771 我们 | 0.32 | 0.02 | - | - | - | - |
Cistern_ValueLinq_Aggregate | 列表 | 3.597 我们 | 0.0718 我们 | 0.1880 我们 | 3.698 我们 | 0.31 | 0.02 | - | - | - | - |