Неисчислимый вопрос: лучшая производительность? - PullRequest
2 голосов
/ 12 ноября 2009

Быстрый вопрос:

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

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

или

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

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

Спасибо

Ответы [ 6 ]

16 голосов
/ 12 ноября 2009

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

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

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

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

7 голосов
/ 12 ноября 2009

В следующем тестовом коде печатаются системные тики (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


      }
6 голосов
/ 12 ноября 2009

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

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

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

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

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

3 голосов
/ 12 ноября 2009

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

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

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

1 голос
/ 12 ноября 2009

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 голосов
/ 09 февраля 2014

Если производительность под вопросом, это, вероятно, не является узким местом, однако, вы рассматривали возможность использования параллельной библиотеки или 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

Надеюсь, это поможет!

...