Производительность Find () против FirstOrDefault () [дубликат]


Аналогичный Вопрос:
найти() и, где().FirstOrDefault ()

получил интересный результат поиска Дианы в большой последовательности простого ссылочного типа, имеющего одно строковое свойство.

using System;
using System.Collections.Generic;
using System.Linq;

public class Customer{
    public string Name {get;set;}
}

Stopwatch watch = new Stopwatch();        
    const string diana = "Diana";

    while (Console.ReadKey().Key != ConsoleKey.Escape)
    {
        //Armour with 1000k++ customers. Wow, should be a product with a great success! :)
        var customers = (from i in Enumerable.Range(0, 1000000)
                         select new Customer
                         {
                            Name = Guid.NewGuid().ToString()
                         }).ToList();

        customers.Insert(999000, new Customer { Name = diana }); // Putting Diana at the end :)

        //1. System.Linq.Enumerable.DefaultOrFirst()
        watch.Restart();
        customers.FirstOrDefault(c => c.Name == diana);
        watch.Stop();
        Console.WriteLine("Diana was found in {0} ms with System.Linq.Enumerable.FirstOrDefault().", watch.ElapsedMilliseconds);

        //2. System.Collections.Generic.List<T>.Find()
        watch.Restart();
        customers.Find(c => c.Name == diana);
        watch.Stop();
        Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List<T>.Find().", watch.ElapsedMilliseconds);
    }

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

Find() работает почти в два раза быстрее, надеясь .Net команда не будет отмечать его устаревшим в будущем.

2 76

2 ответа:

я смог имитировать ваши результаты, поэтому я декомпилировал вашу программу, и есть разница между Find и FirstOrDefault.

во-первых здесь декомпилированная программа. Я сделал ваш объект данных anonmyous элемент данных только для компиляции

    List<\u003C\u003Ef__AnonymousType0<string>> source = Enumerable.ToList(Enumerable.Select(Enumerable.Range(0, 1000000), i =>
    {
      var local_0 = new
      {
        Name = Guid.NewGuid().ToString()
      };
      return local_0;
    }));
    source.Insert(999000, new
    {
      Name = diana
    });
    stopwatch.Restart();
    Enumerable.FirstOrDefault(source, c => c.Name == diana);
    stopwatch.Stop();
    Console.WriteLine("Diana was found in {0} ms with System.Linq.Enumerable.FirstOrDefault().", (object) stopwatch.ElapsedMilliseconds);
    stopwatch.Restart();
    source.Find(c => c.Name == diana);
    stopwatch.Stop();
    Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List<T>.Find().", (object) stopwatch.ElapsedMilliseconds);

главное, чтобы заметить, что FirstOrDefault называется Enumerable, тогда как Find - Это называется как метод в списке источников.

Итак, что же делает find? Это декомпилированный Find метод

private T[] _items;

[__DynamicallyInvokable]
public T Find(Predicate<T> match)
{
  if (match == null)
    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
  for (int index = 0; index < this._size; ++index)
  {
    if (match(this._items[index]))
      return this._items[index];
  }
  return default (T);
}

таким образом, это итерация по массиву элементов, что имеет смысл, так как список является оболочкой на массиве.

, FirstOrDefault на Enumerable класс, использует foreach для перебора элементов. Это использует итератор в список и двигаться дальше. Я думаю, что вы видите накладные расходы итератора
[__DynamicallyInvokable]
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
  if (source == null)
    throw Error.ArgumentNull("source");
  if (predicate == null)
    throw Error.ArgumentNull("predicate");
  foreach (TSource source1 in source)
  {
    if (predicate(source1))
      return source1;
  }
  return default (TSource);
}

Foreach-это просто синтаксический сахар при использовании перечислимого шаблона. Посмотрите на это изображение

enter image description here.

я нажал на foreach, чтобы увидеть, что он делает, и вы можете видеть, что dotpeek хочет взять меня в перечислитель/текущие/следующие реализации, что имеет смысл.

кроме того, что они в основном одинаковы (тестирование переданного предиката, чтобы увидеть, является ли элемент тем, что вы хотите)

держу пари, что FirstOrDefault работает через IEnumerable реализации, то есть, он будет использовать стандартный foreach цикл для выполнения проверки. List<T>.Find() не является частью Linq (http://msdn.microsoft.com/en-us/library/x0b5b5bc.aspx), и, вероятно, используя стандартный for контур 0 до Count (или другой быстрый внутренний механизм, вероятно, работающий непосредственно на его внутреннем/обернутом массиве). Избавившись от накладных расходов на перечисление (и делать версии проверяет, чтобы убедиться, что список не был изменен)Find метод быстрее.

если вы добавите третий тест:

//3. System.Collections.Generic.List<T> foreach
Func<Customer, bool> dianaCheck = c => c.Name == diana;
watch.Restart();
foreach(var c in customers)
{
    if (dianaCheck(c))
        break;
}
watch.Stop();
Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List<T> foreach.", watch.ElapsedMilliseconds);

который работает примерно с той же скоростью, что и первый (25 мс против 27 мс для FirstOrDefault)

EDIT: если я добавлю цикл массива, он становится довольно близко к Find() скорость, и учитывая @devshorts заглянуть в исходный код, я думаю, что это он:

//4. System.Collections.Generic.List<T> for loop
var customersArray = customers.ToArray();
watch.Restart();
int customersCount = customersArray.Length;
for (int i = 0; i < customersCount; i++)
{
    if (dianaCheck(customers[i]))
        break;
}
watch.Stop();
Console.WriteLine("Diana was found in {0} ms with an array for loop.", watch.ElapsedMilliseconds);

это работает только на 5,5% медленнее, чем Find() метод.

так итог: зацикливание элементов массива происходит быстрее, чем работа с foreach накладные расходы итерации. (но у обоих есть свои плюсы / минусы, поэтому просто выберите то, что имеет смысл для вашего кода логически. Кроме того, лишь в редких случаях будет небольшая разница в скорости когда-нибудь вызвать проблему, так что просто используйте то, что имеет смысл для ремонтопригодности / читаемости)