2009-11-12 8 views
2

Быстрый вопрос:IEnumerable вопрос: Лучшая производительность?

Какой из них быстрее?

foreach (Object obj in Collection) 
{ 
    if(obj.Mandatory){ ... } 
} 

или

foreach (Object obj in Collection.FindAll(o => o.Mandatory)) 
{ 
... 
} 

и если вы знаете более быстрое предложение, я был бы рад узнать.

Спасибо

+8

Почему бы не измерить и не узнать? – Brian

+11

@Brian - это не очень приветливое отношение. –

+0

Просто хотел разобрать и посмотреть, что думают люди. В любом случае, спасибо. –

ответ

7

Следующего тест код печатает систему клещей (1 тик = 100 наносекунд) для перебора 10 миллионов объектов. FindAll является самым медленным, и цикл for является самым быстрым, как ожидалось.

Но накладные расходы итерации измеряются в наносекундах за элемент даже в худшем случае. Если вы делаете что-то существенное в цикле (например, что-то, что занимает микросекунду на элемент), то разница в скорости итерации равна совершенно несущественной.

Так что для любви к Тьюрингу не запрещайте foreach в ваших руководствах по кодированию сейчас. Это не делает каких-либо практических различий, и инструкции LINQ уверены, что их легче читать.

public class Test 
    { 
     public bool Bool { get; set; } 
    } 

    class Program 
    { 

     static void Main(string[] args) 
     { 
     // fill test list 
     var list = new List<Test>(); 
     for (int i=0; i<1e7; i++) 
     { 
      list.Add(new Test() { Bool = (i % 2 == 0) }); 
     } 

     // warm-up 
     int counter = 0; 
     DateTime start = DateTime.Now; 
     for (int i = 0; i < list.Count; i++) 
     { 
      if (list[i].Bool) 
      { 
       counter++; 
      } 
     } 

     // List.FindAll 
     counter = 0; 
     start = DateTime.Now; 
     foreach (var test in list.FindAll(x => x.Bool)) 
     { 
      counter++; 
     } 
     Console.WriteLine(DateTime.Now.Ticks - start.Ticks); // prints 7969158 

     // IEnumerable.Where 
     counter = 0; 
      start = DateTime.Now; 
     foreach (var test in list.Where(x => x.Bool)) 
     { 
      counter++; 
     } 
     Console.WriteLine(DateTime.Now.Ticks - start.Ticks); // prints 5156514 

     // for loop 
     counter = 0; 
     start = DateTime.Now; 
     for (int i = 0; i < list.Count; i++) 
     { 
      if (list[i].Bool) 
      { 
       counter++; 
      } 
     } 
     Console.WriteLine(DateTime.Now.Ticks - start.Ticks); // prints 2968902 


     } 
+0

Это неверно. Иногда 'IEnumerable' ужасно медленный по сравнению с необработанными массивами. Но я согласен, не избегайте 'IEnumerable', он очень изящный. Всегда профиль, а когда он медленный, и вы используете 'IEnumerable', переключитесь на массив или' List'. Например, я только что установил цикл 'yield', итерации массива' ListView' SelectedIndices', который привязал процессор и заморозил пользовательский интерфейс почти 2 секунды, прежде чем отображать контекстное меню с несколькими сотнями выбранных элементов. Я изменил его, чтобы использовать '.CopyTo' массив, за которым следует старомодный цикл с индексом' int'. Сейчас мгновение. – doug65536

16

Если ваш Collection является List<T> того FindAll реализуется путем создания нового List<T> и копировать все элементы, которые соответствуют предикату. Это, очевидно, медленнее, чем просто перечисление коллекции и принятие решения по каждому элементу, если предикат выполняется.

Если вы используете .NET 3.5 вы можете использовать LINQ, который не будет создавать копию и и похож на ваш первый пример:

foreach (object obj in someCollection.Where(o => o.Mandatory)) 
{ 
    ... 
} 

Примечание это не обязательно самым быстрым решением. Легко видеть, что метод, который выделяет память и, перечисляет коллекцию медленнее, чем метод, который только перечисляет коллекцию. Если производительность критическая: измерьте ее.

+0

Я собирался опубликовать именно это. Отличный ответ. –

+0

спасибо. Я чувствовал, что FindAll был медленнее, но это было чем-то другим, кроме тестирования каждого элемента. –

+0

Производительность, foreach с if-предложением будет быстрее. Использование. Будет немного медленнее, и использование .FindAll будет * намного медленнее. –

3

Самый быстрый вы могли когда-либо получить без распараллеливания перечисления в несколько потоков, принимая счета числа процессоров и т.д.:

for (int i = 0; i < Collection.Count; i++) 
{ 
    var item = Collection[i]; 
    if (item.Mandatory) { ... } 
} 

Я рекомендовал бы вам хотя всегда использовать Linq вместо того, чтобы писать for или foreach петель, потому что в будущем он станет настолько умным, что на самом деле он сможет распределять работу над процессорами и учитывать специфические вещи оборудования (см. PLinq), и в конечном итоге это будет быстрее, чем если бы вы сами написали петли: декларативное и императивное программирование.

+2

Я не уверен, что это будет быстрее. Вы должны были бы профилировать его, чтобы быть уверенным. В любом случае разница будет очень маленькой. –

+4

Если что-то не изменилось совсем недавно, это немного быстрее, чем foreach, потому что вы не создаете новый объект перечислителя и не вызываете его методы по ходу дела. Разница достаточно мала, чтобы не иметь значения, если ваш код не является чрезвычайно критическим местом. –

+2

Этот метод не «самый быстрый, который вы когда-либо могли получить» - назначение локальной переменной (var item = Collection [i]) не требуется - быстрее вызывать «if (Collection [i] .Mandatory) {.. .} "... –

6

Первый будет несколько быстрее.

Во втором случае вы используете List<T>.FindAll для создания временного списка, который соответствует вашим критериям. Это копирует список, а затем повторяет его.

Однако, вы могли бы сделать то же самое, с той же скоростью, как ваш первый вариант, выполнив:

foreach (Object obj in Collection.Where(o => o.Mandatory)) 
{ 
} 

Это потому, что Enumerable.Where использует потоковое, чтобы возвращать IEnumerable<T>, который генерируется, как вы итерацию. Никакой копии не производится.

+0

Я должен согласиться с этим.Где() - метод расширения, который статически ставится против списка, так что объект не воссоздается. Кроме того, цикл будет работать с возвратом доходности для каждого отдельного значения. –

1

FindAll - это просто синтаксический сахар. Например:

List<string> myStrings = new List<string>(); 
    foreach (string str in myStrings.FindAll(o => o.Length > 0)) 
    { 

    } 

компилируется:

List<string> list = new List<string>(); 
if (CS$<>9__CachedAnonymousMethodDelegate1 == null) 
{ 
    CS$<>9__CachedAnonymousMethodDelegate1 = new Predicate<string>(MyClass.<RunSnippet>b__0); 
} 
using (List<string>.Enumerator enumerator = list.FindAll(CS$<>9__CachedAnonymousMethodDelegate1).GetEnumerator()) 
{ 
    while (enumerator.MoveNext()) 
    { 
     string current = enumerator.Current; 
    } 
} 

public List<T> FindAll(Predicate<T> match) 
{ 
    if (match == null) 
    { 
     ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); 
    } 
    List<T> list = new List<T>(); 
    for (int i = 0; i < this._size; i++) 
    { 
     if (match(this._items[i])) 
     { 
      list.Add(this._items[i]); 
     } 
    } 
    return list; 
} 

private static bool <RunSnippet>b__0(string o) 
{ 
    return (o.Length > 0); 
} 
0

Если производительность в вопросе это, вероятно, не является узким местом, однако, вы считали, используя параллельную библиотеку или PLINQ? смотрите ниже:

Parallel.ForEach(Collection, obj => 
{ 
    if (obj.Mandatory) 
    { 
     DoWork(); 
    } 
}); 

http://msdn.microsoft.com/en-us/library/dd460688(v=vs.110).aspx

Кроме того, хотя, возможно, немного несвязанным кажется, что производительность выглядывает ваше любопытство, если вы имеете дело с очень большими наборами данных, бинарный поиск может быть полезным. В моем случае у меня есть два отдельных списка данных. Мне приходится иметь дело со списками миллионов записей, и это спасло меня буквально экспоненциальным количеством времени на выполнение. Единственным недостатком является то, что он ТОЛЬКО полезен для очень больших коллекций и должен быть отсортирован заранее. Вы также заметите, что это использует класс ConcurrentDictionary, который обеспечивает значительные накладные расходы, но он является потокобезопасным и требовался из-за требований и количества потоков, которыми я управляю асинхронно.

private ConcurrentDictionary<string, string> items; 
private List<string> HashedListSource { get; set; } 
private List<string> HashedListTarget { get; set; } 

this.HashedListTarget.Sort(); 
this.items.OrderBy(x => x.Value); 

private void SetDifferences() 
{ 
    for (int i = 0; i < this.HashedListSource.Count; i++) 
    { 
     if (this.HashedListTarget.BinarySearch(this.HashedListSource[i]) < 0) 
     { 
      this.Mismatch.Add(items.ElementAt(i).Key); 
     } 
    } 
} 

Example displaying the benefits of using Binary Search Этот образ был первоначально размещен в большой статье найти здесь: http://letsalgorithm.blogspot.com/2012/02/intersecting-two-sorted-integer-arrays.html

Надежда это помогает!

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