IEnumerable <int> sourceの合計を見つける3つの異なる実装を、ソースに10,000の整数がある場合にかかる時間とともに以下に示します。
source.Aggregate(0, (result, element) => result + element);
3ミリ秒かかります
source.Sum(c => c);
12ミリ秒かかります
source.Sum();
1ミリ秒かかります
2番目の実装が最初の実装よりも4倍高価なのはなぜでしょうか。 3番目の実装と同じであるべきではありません。
注:私のコンピューターは.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()
と同じように動作するSum(lambda)
を使用して、独自のSelect()
を2回記述して、Sum(lambda)
の使用が実際に理由であることを確認しましょう_フレームワークから、かつSelect()
を使用せずに1回:
_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ミリ秒