のコードcontinue
は約2倍高速です。
LINQPadで次のコードを実行しましたが、結果は一貫してwith句continue
が2倍速いことを示しています。
void Main()
{
var headers = Enumerable.Range(1,1000).ToDictionary(i => "K"+i,i=> i % 2 == 0 ? null : "V"+i);
var stopwatch = new Stopwatch();
var sb = new StringBuilder();
stopwatch.Start();
foreach (var header in headers.Where(header => !String.IsNullOrEmpty(header.Value)))
sb.Append(header);
stopwatch.Stop();
Console.WriteLine("Using LINQ : " + stopwatch.ElapsedTicks);
sb.Clear();
stopwatch.Reset();
stopwatch.Start();
foreach (var header in headers)
{
if (String.IsNullOrEmpty(header.Value)) continue;
sb.Append(header);
}
stopwatch.Stop();
Console.WriteLine("Using continue : " + stopwatch.ElapsedTicks);
}
これが私が得た結果のいくつかです
Using LINQ : 1077
Using continue : 348
Using LINQ : 939
Using continue : 459
Using LINQ : 768
Using continue : 382
Using LINQ : 1256
Using continue : 457
Using LINQ : 875
Using continue : 318
一般に、LINQは、すでに評価されているものを使用するIEnumerable<T>
場合、foreach
対応するものよりも常に遅くなります。その理由は、LINQ-to-Objectsは、これらの低レベル言語機能の高レベルラッパーにすぎないためです。ここでLINQを使用する利点は、パフォーマンスではなく、一貫性のあるインターフェイスの提供です。LINQは絶対にパフォーマンス上の利点を提供しますが、アクティブなメモリにまだ存在しないリソースを操作しているときに効果を発揮します(実際に実行されるコードを最適化する機能を活用できます)。代替コードが最適な代替コードである場合、LINQは冗長なプロセスを実行するだけで、とにかく作成したのと同じコードを呼び出すことができます。これを説明するために、LINQを使用するときに実際に呼び出される以下のコードを貼り付けます。Where
ロードされた列挙型の演算子:
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
if (predicate == null)
{
throw Error.ArgumentNull("predicate");
}
if (source is Iterator<TSource>)
{
return ((Iterator<TSource>) source).Where(predicate);
}
if (source is TSource[])
{
return new WhereArrayIterator<TSource>((TSource[]) source, predicate);
}
if (source is List<TSource>)
{
return new WhereListIterator<TSource>((List<TSource>) source, predicate);
}
return new WhereEnumerableIterator<TSource>(source, predicate);
}
そして、これがWhereSelectEnumerableIterator<TSource,TResult>
クラスです。このpredicate
フィールドは、Where()
メソッドに渡すデリゲートです。メソッド内で実際に実行される場所MoveNext
(およびすべての冗長nullチェック)が表示されます。また、列挙可能なものが1回だけループされていることもわかります。句をスタックwhere
すると、複数のイテレータクラスが作成されます(先行クラスがラップされます)が、複数の列挙アクションは発生しません(実行が延期されるため)。このようなLambdaを作成すると、実際には新しいDelegateインスタンスも作成されることに注意してください(パフォーマンスにもわずかな影響があります)。
private class WhereSelectEnumerableIterator<TSource, TResult> : Enumerable.Iterator<TResult>
{
private IEnumerator<TSource> enumerator;
private Func<TSource, bool> predicate;
private Func<TSource, TResult> selector;
private IEnumerable<TSource> source;
public WhereSelectEnumerableIterator(IEnumerable<TSource> source, Func<TSource, bool> predicate, Func<TSource, TResult> selector)
{
this.source = source;
this.predicate = predicate;
this.selector = selector;
}
public override Enumerable.Iterator<TResult> Clone()
{
return new Enumerable.WhereSelectEnumerableIterator<TSource, TResult>(this.source, this.predicate, this.selector);
}
public override void Dispose()
{
if (this.enumerator != null)
{
this.enumerator.Dispose();
}
this.enumerator = null;
base.Dispose();
}
public override bool MoveNext()
{
switch (base.state)
{
case 1:
this.enumerator = this.source.GetEnumerator();
base.state = 2;
break;
case 2:
break;
default:
goto Label_007C;
}
while (this.enumerator.MoveNext())
{
TSource current = this.enumerator.Current;
if ((this.predicate == null) || this.predicate(current))
{
base.current = this.selector(current);
return true;
}
}
this.Dispose();
Label_007C:
return false;
}
public override IEnumerable<TResult2> Select<TResult2>(Func<TResult, TResult2> selector)
{
return new Enumerable.WhereSelectEnumerableIterator<TSource, TResult2>(this.source, this.predicate, Enumerable.CombineSelectors<TSource, TResult, TResult2>(this.selector, selector));
}
public override IEnumerable<TResult> Where(Func<TResult, bool> predicate)
{
return (IEnumerable<TResult>) new Enumerable.WhereEnumerableIterator<TResult>(this, predicate);
}
}
個人的には、LINQコードの保守と再利用がはるかに簡単であるため、パフォーマンスの違いは完全に正当化できると思います。また、パフォーマンスの問題を相殺するためのことも行います(すべての匿名ラムダデリゲートと式を、共通クラスの静的読み取り専用フィールドとして宣言するなど)。しかし、実際の質問を参照すると、あなたのcontinue
条項はLINQの代替案よりも間違いなく高速です。