34

下面给出了查找IEnumerable <int> 源的总和的三种不同实现,以及源具有 10,000 个整数时所用的时间。

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

需要 3 毫秒

source.Sum(c => c);

需要 12 毫秒

source.Sum();

需要 1 毫秒

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

4

2 回答 2

84

注意:我的计算机运行的是 .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 毫秒
于 2012-06-14T10:51:07.267 回答
0

仅仅晚了十年,但...

我已经研究过 Linq 替换(nugetgithub),它(主要是!)替代 System.Linq (只需添加 nuget 包,然后更改using System.Linqusing 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 - - - -
于 2021-01-13T07:44:26.357 回答