34

IEnumerable <int>ソースの合計を見つける3つの異なる実装を、ソースに10,000個の整数がある場合にかかる時間とともに以下に示します。

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

3ミリ秒かかります

source.Sum(c => c);

12ミリ秒かかります

source.Sum();

1ミリ秒かかります

なぜ2番目の実装が最初の実装より4倍高価なのか疑問に思います。3番目の実装と同じではありません。

4

2 に答える 2

84

注:私のコンピューターは.Net 4.5 RCを実行しているため、結果がこれによって影響を受ける可能性があります。

メソッドの実行にかかる時間を1回だけ測定することは、通常、あまり役に立ちません。これは、実際のコードの実際のボトルネックではないJITコンパイルなどによって簡単に支配される可能性があります。このため、各メソッドの実行を100回測定しました(デバッガーを接続しないリリースモードで)。私の結果は次のとおりです。

  • Aggregate():9ミリ秒
  • Sum(lambda):12ミリ秒
  • Sum():6ミリ秒

最速であるという事実Sum()は驚くべきことではありません。デリゲート呼び出しのない単純なループが含まれているため、非常に高速です。との違いはSum(lambda)Aggregate()測定したものほど顕著ではありませんが、それでもそこにあります。その理由は何でしょうか?2つのメソッドの逆コンパイルされたコードを見てみましょう。

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()、次にイテレータを使用します。また、イテレータを使用するということは、いくらかのオーバーヘッドがあることを意味します。イテレータオブジェクトを作成し、(おそらくもっと重要なことですが)各アイテムに対してもう1つのメソッド呼び出しを作成します。

Select()を使用することが実際の理由であることを確認しましょう。1回はフレームワークと同じように動作するSum(lambda)を使用し、もう1回は使用せずに2回記述します。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

わずか10年遅れですが...

私はLinqの置換(nugetgithub)に取り組んできました。これは、(ほとんどの場合!)System.Linqの置換のドロップです(nugetパッケージを追加してからに変更using System.Linqするusing Cistern.ValueLinqだけです)が、値型と内部でいくつかのトリックを使用しています。

とにかく、それは可能な限り、Sum内部でSIMD命令を使用します(オーバーフローを尊重します)。

以下の結果は、この元のスタックオーバーフローの質問として古いマシンで実行されているため、最新のマシンでもより良い結果が得られるはずです(ただし、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>();
}

方法 ContainerType 平均 エラー StdDev 中央値 比率 RatioSD 第0世代 第1世代 第2世代 割り当て済み
System_Linq_Sum 列挙可能 6.025 us 0.1155 us 0.1501 us 6.041 us 1.00 0.00 0.0076 - - 40 B
System_Linq_Sum_Predicate 列挙可能 8.731私たち 0.1727 us 0.3681 us 8.742私たち 1.45 0.08 - - - 40 B
System_Linq_Aggregate 列挙可能 8.534 us 0.1683 us 0.3514 us 8.657私たち 1.42 0.07 - - - 40 B
Cistern_ValueLinq_Sum 列挙可能 6.907私たち 0.1369 us 0.3061 us 6.911私たち 1.15 0.07 0.0076 - - 40 B
Cistern_ValueLinq_Sum_Predicate 列挙可能 9.769私たち 0.1907 us 0.1784 us 9.823私たち 1.62 0.04 - - - 40 B
Cistern_ValueLinq_Aggregate 列挙可能 9.295私たち 0.1847 us 0.4962 us 9.396私たち 1.52 0.08 - - - 40 B
System_Linq_Sum 配列 6.731私たち 0.1343 us 0.3653 us 6.814私たち 1.00 0.00 0.0076 - - 32 B
System_Linq_Sum_Predicate 配列 9.432私たち 0.1873 us 0.5065 us 9.685私たち 1.41 0.11 - - - 32 B
System_Linq_Aggregate 配列 9.494私たち 0.1890 us 0.4707 us 9.759私たち 1.41 0.11 - - - 32 B
Cistern_ValueLinq_Sum 配列 1.404私たち 0.0279 us 0.0710 us 1.436私たち 0.21 0.02 - - - -
Cistern_ValueLinq_Sum_Predicate 配列 4.064 us 0.0811 us 0.0996 us 4.087私たち 0.61 0.04 - - - -
Cistern_ValueLinq_Aggregate 配列 3.549私たち 0.0709 us 0.1496 us 3.584 us 0.53 0.04 - - - -
System_Linq_Sum リスト 11.779私たち 0.2344 us 0.3048 us 11.854私たち 1.00 0.00 - - - 40 B
System_Linq_Sum_Predicate リスト 14.227私たち 0.2842 us 0.6919 us 14.601私たち 1.22 0.05 - - - 40 B
System_Linq_Aggregate リスト 13.852私たち 0.2761 us 0.7418 us 14.249私たち 1.17 0.06 - - - 40 B
Cistern_ValueLinq_Sum リスト 1.437私たち 0.0288 us 0.0835 us 1.471私たち 0.12 0.01 - - - -
Cistern_ValueLinq_Sum_Predicate リスト 3.672私たち 0.0732 us 0.1941 us 3.771私たち 0.32 0.02 - - - -
Cistern_ValueLinq_Aggregate リスト 3.597私たち 0.0718 us 0.1880 us 3.698私たち 0.31 0.02 - - - -
于 2021-01-13T07:44:26.357 に答える