1

Reflectorを介してSystem.Linq.Enumerableを見ると、 SelectおよびWhere拡張メソッドに使用されるデフォルトのイテレーター( WhereSelectArrayIterator )がICollectionインターフェイスを実装していないことに気付きました。コードを正しく読み取ると、Count()ToList( )などの他の拡張メソッドのパフォーマンスが低下します。

public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
    // code above snipped
    if (source is List<TSource>)
    {
        return new WhereSelectListIterator<TSource, TResult>((List<TSource>) source, null, selector);
    }
    // code below snipped
}

private class WhereSelectListIterator<TSource, TResult> : Enumerable.Iterator<TResult>
{
    // Fields
    private List<TSource> source; // class has access to List source so can implement ICollection
    // code below snipped
}


public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable
{
public List(IEnumerable<T> collection)
{
    ICollection<T> is2 = collection as ICollection<T>;
    if (is2 != null)
    {
        int count = is2.Count;
        this._items = new T[count];
        is2.CopyTo(this._items, 0); // FAST
        this._size = count;
    }
    else
    {
        this._size = 0;
        this._items = new T[4];
        using (IEnumerator<T> enumerator = collection.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                this.Add(enumerator.Current);  // SLOW, CAUSES ARRAY EXPANSION
            }
        }
    }
}

}

私はこれをテストし、疑いを確認する結果を出しました。

ICollection:2388.5222ミリ秒

IEnumerable:3308.3382ミリ秒

テストコードは次のとおりです。

    // prepare source
    var n = 10000;
    var source = new List<int>(n);
    for (int i = 0; i < n; i++) source.Add(i);

    // Test List creation using ICollection
    var startTime = DateTime.Now;
    for (int i = 0; i < n; i++)
    {
        foreach(int l in source.Select(k => k)); // itterate to make comparison fair
        new List<int>(source);
    }
    var finishTime = DateTime.Now;
    Response.Write("ICollection: " + (finishTime - startTime).TotalMilliseconds + " ms <br />");

    // Test List creation using IEnumerable
    startTime = DateTime.Now;
    for (int i = 0; i < n; i++) new List<int>(source.Select(k => k));
    finishTime = DateTime.Now;
    Response.Write("IEnumerable: " + (finishTime - startTime).TotalMilliseconds + " ms");

私は何かが足りないのですか、それともこれはフレームワークの将来のバージョンで修正されますか?

考えていただきありがとうございます。

4

1 に答える 1

5

LINQ to Objectsは、いくつかのトリックを使用して特定の操作を最適化します。たとえば、2つの.Whereステートメントをチェーン化すると、述語が1つに結合されるWhereArrayIteratorため、前のステートメントをガベージコレクションできます。同様に、Whereその後にaが続くと、Selectが作成されWhereSelectArrayIterator、結合された述語が引数として渡されるため、元の述語をWhereArrayiteratorガベージコレクションできます。したがって、は、に基づいているかどうかに関係なく、を追跡するだけでなく、組み合わせWhereSelectArrayIteratorを追跡する責任があります。selectorpredicate

このsourceフィールドは、指定された最初のリストのみを追跡します。述語のため、反復結果には常に同じ数のアイテムが含まれるとは限りsourceません。LINQは遅延評価を目的としているため、誰かが電話をかけてしまった場合に時間を節約できる可能性があるという理由だけsourceで、事前に評価するべきではありません。これにより、手動で呼び出すのと同じくらいパフォーマンスが低下し、ユーザーが複数の句を実行すると、不必要に複数のリストを作成することになります。predicate.Count().ToList()WhereSelect

LINQ to Objectsをリファクタリングして、配列で直接呼び出されたSelectArrayIteratorときに使用するを作成できますか?Selectもちろん。パフォーマンスが向上しますか?若干。どのくらいの費用で?コードの再利用が少ないということは、今後も維持およびテストするための追加のコードを意味します。

したがって、「言語/プラットフォームXに機能Yがない理由」の質問の大部分の核心にたどり着きます。すべての機能と最適化にはそれに関連するコストがあり、Microsoftでさえ無制限のリソースを持っていません。他のすべての企業と同じように、彼らは判断を求めてSelect、配列に対して実行するコードが実行される頻度を決定し、それを呼び出します.ToList()。また、その実行を少し速くすることは、LINQで別のクラスを作成して維持する価値があるかどうかを判断します。パッケージ。

于 2011-08-16T22:16:46.473 に答える