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