1

在通过 Reflector 查看System.Linq.Enumerable时,我注意到用于SelectWhere扩展方法的默认迭代器 - 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 使用一些技巧来优化某些操作。例如,如果将两个.Where语句链接在一起,谓词将组合成一个WhereArrayIterator,因此可以对前面的语句进行垃圾回收。同样, aWhere后跟 aSelect将创建 a WhereSelectArrayIterator,将组合谓词作为参数传递,以便可以对原始谓词WhereArrayiterator进行垃圾收集。因此,WhereSelectArrayIterator不仅负责跟踪selector,还负责跟踪predicate它可能基于或可能不基于的组合。

source字段仅跟踪给出的初始列表。由于谓词,迭代结果并不总是具有相同数量的项目source。由于 LINQ 旨在进行延迟评估,因此它不应该source提前predicate评估.Count(). 这将导致与手动调用它一样多的性能损失.ToList(),并且如果用户通过多个WhereandSelect子句运行它,您最终会不必要地构建多个列表。

LINQ to Objects 是否可以重构以创建在直接在数组上调用SelectArrayIterator时使用的对象?Select当然。它会提高性能吗?一点点。以什么代价?更少的代码重用意味着需要额外的代码来维护和测试前进。

因此,我们找到了绝大多数“为什么语言/平台 X 没有功能 Y”问题的症结所在:每个功能和优化都有一些与之相关的成本,甚至微软也没有无限的资源。就像其他所有公司一样,他们做出判断调用以确定Select对数组执行 a 然后调用.ToList()它的代码的运行频率,以及是否值得在 LINQ 中编写和维护另一个类包裹。

于 2011-08-16T22:16:46.473 回答