2011-08-16 2 views
1

Глядя на System.Linq.Enumerable через отражатель я заметил, что по умолчанию итератор используется для Выбрать и Где методы расширения - WhereSelectArrayIterator - не реализует ICollection интерфейса. Если я прочитал код правильно это вызывает некоторые другие методы расширения, такие как Count() и ToList() работает медленнее:Почему не WhereSelectArrayIterator реализует ICollection?

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 мс

IEnumerabl е: 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"); 

Я-то отсутствует или это будет исправлено в будущих версиях рамки?

Благодарим вас за мысли.

ответ

5

LINQ to Objects использует некоторые трюки для оптимизации определенных операций. Например, если вы объединяете два оператора .Where, предикаты будут объединены в один WhereArrayIterator, поэтому предыдущие могут быть собраны в мусор. Аналогично, Where, за которым следует Select, будет создан WhereSelectArrayIterator, передавая объединенные предикаты в качестве аргумента, чтобы исходный WhereArrayiterator мог быть собран в мусор. Таким образом, WhereSelectArrayIterator отвечает за отслеживание не только selector, но и объединенного predicate, на котором он может быть основан или нет.

Поле source сохраняет только начальный список. Из-за предиката результат итерации не всегда будет иметь такое же количество элементов, как source. Поскольку LINQ предназначен для ленивой оценки, он не должен заранее оценивать source против predicate, чтобы он мог потенциально сэкономить время, если кто-то закончит вызов .Count(). Это приведет к такой же высокой производительности, как и вызов .ToList() на нем вручную, и если пользователь выполнил ее через несколько статей Where и Select, вы бы закончили тем, что делали ненужным создание нескольких списков.

Может ли реконструировать объекты LINQ to Objects для создания SelectArrayIterator, которые он использует, когда Select вызывается непосредственно на массив? Конечно. Будет ли это повышать производительность? Немного. По какой цене? Меньше повторного использования кода означает дополнительный код для поддержания и проверки движения вперед.

И таким образом мы добираемся до сути огромного большинства вопросов «Почему язык/платформа X не имеют функции Y»: каждая функция и оптимизация связаны с некоторыми издержками, и даже у Microsoft нет неограниченного количества Ресурсы.Как и любая другая компания, они вызывают суждения, чтобы определить, как часто будет выполняться код, который выполняет Select на массиве, а затем вызывает на нем .ToList(), и стоит ли делать этот запуск немного быстрее, стоит писать и поддерживать другой класс в пакет LINQ.

+0

Спасибо - это то, что надоедливый предикат, который мешает ему заранее знать его размер. Я бы сделал случай, когда списки процессов, не изменяя их размер, являются частыми действиями, и, возможно, whereSelectIterrator может быть подклассом SelectIterrator, который _could_ реализует icollection. В любом случае, еще раз спасибо. –

+0

на самом деле, поцарапать это, отменить его: WhereSelectIterrator должен быть родительским классом, а SelectIterrator должен быть подклассом, реализующим ICollection –

Смежные вопросы