Производительность поиска() vs. FirstOrDefault()

Возможный дубликат:
Найти() vs. Where(). FirstOrDefault()

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

Stopwatch watch = new Stopwatch();        
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);
}

enter image description here

Это из-за отсутствия служебных ресурсов Enumerator в List.Find() или этого плюс, возможно, что-то еще?

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

Ответ 1

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

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

    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 метод

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 хочет взять меня в enumerator/current/next реализаций, что имеет смысл.

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

Ответ 2

Я ставлю, что 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 накладных расходов итерации. (но оба имеют свои плюсы и минусы, поэтому просто выбирайте, что имеет смысл для вашего логического кода). Кроме того, только редко небольшая разница в скорости когда-либо вызывает проблему, поэтому просто используйте то, что имеет смысл для удобства и удобочитаемости).