2009-10-07 2 views
26

Я просто хочу знать, будет ли «FindAll» быстрее, чем «Где» extentionMethod и почему?FindAll vs Где расширение-метод

Пример:

myList.FindAll(item=> item.category == 5); 

или

myList.Where(item=> item.category == 5); 

Что лучше?

+0

@Kevin: btw «FindAll» не является методом расширения –

ответ

47

Ну, 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); 
    } 
} 
+0

Я очень не уверен в вашем утверждении, потому что, если я использую findAll, и я изменяю свою коллекцию результатов, первоначальная коллекция изменяется, поэтому не может быть копией –

+2

, если элемент является значением, а не ссылкой, вы увидите copy – Benny

+1

Как вы изменили сбор результатов? Я предполагаю, что вы меняли данные в одном из элементов. Список содержит только ссылки (при условии, что T является ссылочным типом). Изменение содержимого объекта на самом деле не изменяет сам список. Вы увидите такое же поведение любым другим способом. Однако сам список * *, возвращаемый FindAll, не зависит от исходного списка, например, если вы добавляете новый элемент в исходный список * после * вызова FindAll, который не отображается в списке результатов. –

2

Ну, по крайней мере, вы можете попытаться его измерить.

Статический метод Where реализован с использованием блока итератора (yield), что в основном означает, что выполнение будет отложено. Если вы просто сравниваете вызовы с тезисами на два метода, первый будет медленнее, так как он сразу же предполагает, что вся коллекция будет итерации.

Но если вы включите полную итерацию результатов, которые вы получите, все может быть немного иначе. Я уверен, что решение yield работает медленнее, из-за созданного механизма машинного механизма. (см. @Matthew anwser)

+0

Я не уверен, что у обоих решений есть один и тот же алгоритм. –

+0

Вы правы, в FindAll не используется блок итераторов. Это может быть немного медленнее, чем метод static Where. –

1

я могу дать некоторые подсказки, но не уверен, какой из них быстрее. Функция FindAll() выполняется сразу. Где() исполняется defferred.

13

Как насчет того, чтобы мы тестировали, а не предполагали? Позор, чтобы увидеть неправильный ответ.

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 
/* 
+2

+1, но обратите внимание, что если вам не нужно делать ToList после Where, это, вероятно, будет быстрее и займет меньше памяти. –

+0

Это может быть правдой. но вы должны что-то сделать с тем, где. И если вы .ToArray, результаты одинаковы. Если вы посмотрите на код, лежащий в основе двух методов, вы увидите, что больше работает. –

+0

Если вы не нанеселись на то, что получится на ваших результатах? –

0

Преимущество где является отсроченным исполнением.Чувствуете разницу, если вы хотите иметь следующие функциональные возможности

BigSequence.FindAll(x => DoIt(x)).First(); 
BigSequence.Where(x => DoIt(x)).First(); 

FindAll покрыл полный sequene, а где в большинстве последовательностей перестанут перечисляя, как только найден один элемент.

Те же эффекты будут один с помощью Any(), Take(), Skip() и т.д. Я не уверен, но я думаю, вы будете иметь огромные преимущества во всех функциях, которые отсроченное исполнение