Я просто хочу знать, будет ли «FindAll» быстрее, чем «Где» extentionMethod и почему?FindAll vs Где расширение-метод
Пример:
myList.FindAll(item=> item.category == 5);
или
myList.Where(item=> item.category == 5);
Что лучше?
Я просто хочу знать, будет ли «FindAll» быстрее, чем «Где» extentionMethod и почему?FindAll vs Где расширение-метод
Пример:
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
(Составлено с/о +/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);
}
}
Я очень не уверен в вашем утверждении, потому что, если я использую findAll, и я изменяю свою коллекцию результатов, первоначальная коллекция изменяется, поэтому не может быть копией –
, если элемент является значением, а не ссылкой, вы увидите copy – Benny
Как вы изменили сбор результатов? Я предполагаю, что вы меняли данные в одном из элементов. Список содержит только ссылки (при условии, что T является ссылочным типом). Изменение содержимого объекта на самом деле не изменяет сам список. Вы увидите такое же поведение любым другим способом. Однако сам список * *, возвращаемый FindAll, не зависит от исходного списка, например, если вы добавляете новый элемент в исходный список * после * вызова FindAll, который не отображается в списке результатов. –
Ну, по крайней мере, вы можете попытаться его измерить.
Статический метод Where
реализован с использованием блока итератора (yield
), что в основном означает, что выполнение будет отложено. Если вы просто сравниваете вызовы с тезисами на два метода, первый будет медленнее, так как он сразу же предполагает, что вся коллекция будет итерации.
Но если вы включите полную итерацию результатов, которые вы получите, все может быть немного иначе. Я уверен, что решение yield
работает медленнее, из-за созданного механизма машинного механизма. (см. @Matthew anwser)
Я не уверен, что у обоих решений есть один и тот же алгоритм. –
Вы правы, в FindAll не используется блок итераторов. Это может быть немного медленнее, чем метод static Where. –
я могу дать некоторые подсказки, но не уверен, какой из них быстрее. Функция FindAll() выполняется сразу. Где() исполняется defferred.
Как насчет того, чтобы мы тестировали, а не предполагали? Позор, чтобы увидеть неправильный ответ.
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();
... до ...
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
*/
Edit 2,0 ...
Если вы хотите получить список подмножества текущего списка наиболее быстрый способ, если FindAll(). Причина этого проста. Метод экземпляра FindAll использует индексатор в текущем списке вместо конечного автомата перечисления. Метод расширения Where() - это внешний вызов другого класса, который использует перечислитель. Если вы перейдете от каждого узла в списке к следующему узлу, вам придется вызвать метод MoveNext() под обложками. Как видно из приведенных выше примеров, еще быстрее использовать записи индекса для создания нового списка (который указывает на исходные элементы, так что раздутие памяти будет минимальным), чтобы даже просто получить количество фильтрованных элементов.
Теперь, если вы собираетесь выполнить досрочное прерывание с помощью Enumerator, метод Where() может быть быстрее. Конечно, если вы переместите логику раннего прерывания в предикат метода FindAll(), вы снова будете использовать индексатор вместо перечислителя.
Теперь есть другие причины использовать инструкцию Where() (например, другие методы linq, блоки foreach и многое другое), но вопрос был в том, что FindAll() быстрее, чем Where(). И если вы не выполняете Where(), ответ кажется да. (При сравнении яблок с яблоками)
Я не говорю, что не использую LINQ или метод Where(). Они делают код, который намного проще читать. Вопрос был о производительности, а не о том, как легко вы можете читать и понимать код. Быстро самым быстрым способом выполнить эту работу было бы использование блока для каждого шага для каждого индекса и выполнения любой логики, как вы хотите (даже ранних выходов). Причина, по которой LINQ настолько велика, объясняется сложными деревьями выражений и трансформацией, с которыми вы можете справиться. Но использование итератора из метода .Where() должно идти, хотя тонны кода, чтобы найти способ использования в памяти statemachine, который просто выводит следующий индекс из списка. Следует также отметить, что этот метод .FindAll() используется только на объектах, которые implmented его (например, массив и список.)
Еще больше ...
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
/*
+1, но обратите внимание, что если вам не нужно делать ToList после Where, это, вероятно, будет быстрее и займет меньше памяти. –
Это может быть правдой. но вы должны что-то сделать с тем, где. И если вы .ToArray, результаты одинаковы. Если вы посмотрите на код, лежащий в основе двух методов, вы увидите, что больше работает. –
Если вы не нанеселись на то, что получится на ваших результатах? –
Преимущество где является отсроченным исполнением.Чувствуете разницу, если вы хотите иметь следующие функциональные возможности
BigSequence.FindAll(x => DoIt(x)).First();
BigSequence.Where(x => DoIt(x)).First();
FindAll покрыл полный sequene, а где в большинстве последовательностей перестанут перечисляя, как только найден один элемент.
Те же эффекты будут один с помощью Any(), Take(), Skip() и т.д. Я не уверен, но я думаю, вы будете иметь огромные преимущества во всех функциях, которые отсроченное исполнение
@Kevin: btw «FindAll» не является методом расширения –