Я просто хочу знать, будет ли "FindAll" быстрее, чем "Где" extentionMethod и почему?
Пример:
myList.FindAll(item=> item.category == 5);
или
myList.Where(item=> item.category == 5);
Что лучше?
Я просто хочу знать, будет ли "FindAll" быстрее, чем "Где" extentionMethod и почему?
Пример:
myList.FindAll(item=> item.category == 5);
или
myList.Where(item=> item.category == 5);
Что лучше?
Ну, FindAll
копирует соответствующие элементы в новый список, тогда как Where
просто возвращает лениво оцениваемую последовательность - копирование не требуется.
Поэтому я ожидаю, что Where
будет немного быстрее, чем FindAll
, даже когда итоговая последовательность будет полностью оценена - и, конечно, ленивая стратегия оценки Where
означает, что если вы только посмотрите (скажем) первого совпадения, ему не нужно будет проверять оставшуюся часть списка. (Как указывает Мэттью, там работает поддержка машины состояний Where
. Однако это будет иметь фиксированную стоимость памяти, тогда как для создания нового списка может потребоваться множество распределений массивов и т.д.)
В принципе, FindAll(predicate)
ближе к Where(predicate).ToList()
, чем к просто Where(predicate)
.
Чтобы немного ответить на ответ Мэтью, я не думаю, что он достаточно тщательно проверил его. Его предикат случается, чтобы выбрать половину предметов. Здесь короткая, но полная программа, которая проверяет один и тот же список, но с тремя разными предикатами: один не выбирает предметов, один выбирает все предметы, а один выбирает половину из них. В каждом случае я запускаю тест пятьдесят раз, чтобы получить более длительный срок.
Я использую Count()
, чтобы убедиться, что результат Where
полностью оценивается. Результаты показывают, что, собрав около половины результатов, они - шея и шея. Не получая результатов, FindAll
побеждает. Собирая все результаты, выигрывает Where
. Я нахожу это интригующим: все решения становятся медленнее, так как все больше и больше совпадений найдено: FindAll
имеет больше операций копирования, а Where
должен возвращать согласованные значения вместо простого цикла в реализации MoveNext()
. Однако FindAll
работает медленнее, чем Where
, поэтому теряет свое раннее лидерство. Очень интересно.
Результаты:
FindAll: All: 11994
Where: All: 8176
FindAll: Half: 6887
Where: Half: 6844
FindAll: None: 3253
Where: None: 4891
(Скомпилирован с /o +/debug- и запущен из командной строки,.NET 3.5.)
код:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
class Test
{
static List<int> ints = Enumerable.Range(0, 10000000).ToList();
static void Main(string[] args)
{
Benchmark("All", i => i >= 0); // Match all
Benchmark("Half", i => i % 2 == 0); // Match half
Benchmark("None", i => i < 0); // Match none
}
static void Benchmark(string name, Predicate<int> predicate)
{
// We could just use new Func<int, bool>(predicate) but that
// would create one delegate wrapping another.
Func<int, bool> func = (Func<int, bool>)
Delegate.CreateDelegate(typeof(Func<int, bool>), predicate.Target,
predicate.Method);
Benchmark("FindAll: " + name, () => ints.FindAll(predicate));
Benchmark("Where: " + name, () => ints.Where(func).Count());
}
static void Benchmark(string name, Action action)
{
GC.Collect();
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 50; i++)
{
action();
}
sw.Stop();
Console.WriteLine("{0}: {1}", name, sw.ElapsedMilliseconds);
}
}
Как насчет того, чтобы мы тестировали, а не предполагали? Позор, чтобы увидеть неправильный ответ выйти.
var ints = Enumerable.Range(0, 10000000).ToList();
var sw1 = Stopwatch.StartNew();
var findall = ints.FindAll(i => i % 2 == 0);
sw1.Stop();
var sw2 = Stopwatch.StartNew();
var where = ints.Where(i => i % 2 == 0).ToList();
sw2.Stop();
Console.WriteLine("sw1: {0}", sw1.ElapsedTicks);
Console.WriteLine("sw2: {0}", sw2.ElapsedTicks);
/*
Debug
sw1: 1149856
sw2: 1652284
Release
sw1: 532194
sw2: 1016524
*/
Edit:
Даже если я перевернувший код из
var findall = ints.FindAll(i => i % 2 == 0);
...
var where = ints.Where(i => i % 2 == 0).ToList();
... to...
var findall = ints.FindAll(i => i % 2 == 0).Count;
...
var where = ints.Where(i => i % 2 == 0).Count();
Получаю эти результаты
/*
Debug
sw1: 1250409
sw2: 1267016
Release
sw1: 539536
sw2: 600361
*/
Изменить 2.0...
Если вы хотите, чтобы список подмножества текущего списка был самым быстрым методом, если FindAll(). Причина этого проста. Метод экземпляра FindAll использует индексатор в текущем списке вместо конечного автомата перечисления. Метод расширения Where() - это внешний вызов другого класса, который использует перечислитель. Если вы перейдете от каждого node в списке к следующему node, вам нужно будет вызвать метод MoveNext() под обложками. Как видно из приведенных выше примеров, еще быстрее использовать записи индекса для создания нового списка (который указывает на исходные элементы, так что раздутие памяти будет минимальным), чтобы даже просто получить количество фильтрованных элементов.
Теперь, если вы собираетесь досрочно прекратить работу с Enumerator, метод Where() может быть быстрее. Конечно, если вы переместите логику раннего прерывания в предикат метода FindAll(), вы снова будете использовать индексатор вместо перечислителя.
Теперь есть другие причины использовать инструкцию Where() (например, другие методы linq, блоки foreach и многое другое), но вопрос был в FindAll() быстрее, чем Where(). И если вы не выполняете Where(), ответ кажется да. (При сравнении яблок с яблоками)
Я не говорю, не используйте LINQ или метод .Where(). Они делают код, который намного проще читать. Вопрос был о производительности, а не о том, как легко вы можете читать и понимать код. Быстро самым быстрым способом выполнить эту работу было бы использование блока для каждого шага для каждого индекса и выполнения любой логики, как вы хотите (даже ранних выходов). Причина, по которой LINQ настолько велика, объясняется сложными деревьями выражений и трансформацией, с которыми вы можете справиться. Но использование итератора из метода .Where() должно идти, хотя тонны кода, чтобы найти его способ в statemachine в памяти, который просто выводит следующий индекс из списка. Следует также отметить, что этот метод .FindAll() полезен только для объектов, которые имплантировали его (например, Array и List.)
Еще больше...
for (int x = 0; x < 20; x++)
{
var ints = Enumerable.Range(0, 10000000).ToList();
var sw1 = Stopwatch.StartNew();
var findall = ints.FindAll(i => i % 2 == 0).Count;
sw1.Stop();
var sw2 = Stopwatch.StartNew();
var where = ints.AsEnumerable().Where(i => i % 2 == 0).Count();
sw2.Stop();
var sw4 = Stopwatch.StartNew();
var cntForeach = 0;
foreach (var item in ints)
if (item % 2 == 0)
cntForeach++;
sw4.Stop();
Console.WriteLine("sw1: {0}", sw1.ElapsedTicks);
Console.WriteLine("sw2: {0}", sw2.ElapsedTicks);
Console.WriteLine("sw4: {0}", sw4.ElapsedTicks);
}
/* averaged results
sw1 575446.8
sw2 605954.05
sw3 394506.4
/*
Ну, по крайней мере, вы можете попытаться его измерить.
Статический метод Where
реализуется с использованием блока iterator (yield
), что в основном означает, что выполнение будет отложено. Если вы просто сравниваете вызовы с тезисами на два метода, первый будет медленнее, так как он сразу же предполагает, что вся коллекция будет итерации.
Но если вы включите полную итерацию полученных результатов, все может быть немного иначе. Я уверен, что решение yield
работает медленнее из-за созданного механизма машинного механизма. (см. @Matthew anwser)
Я могу дать некоторые подсказки, но не уверен, какой из них быстрее. FindAll() выполняется сразу. Выполняется выполнение операции().
Преимущество: отсроченное исполнение. Посмотрите разницу, если у вас будет следующая функциональность
BigSequence.FindAll( x => DoIt(x) ).First();
BigSequence.Where( x => DoIt(x) ).First();
FindAll охватил полную секвенцию, в то время как в большинстве последовательностей перестает перечисляться, как только будет найден один элемент.
Те же эффекты будут использоваться с помощью Any(), Take(), Skip() и т.д. Я не уверен, но, я думаю, у вас будут огромные преимущества во всех функциях, которые имеют отложенное выполнение.