FindAll, по сравнению с Где дополнительный метод

Это - оптимизация в случае, что regex включает ссылку на переменную. Это указывает, что regex не изменяется даже при том, что это имеет переменную в нем. Это допускает оптимизацию, которая не была бы возможна иначе.

33
задан Cédric Boivin 7 October 2009 в 14:17
поделиться

4 ответа

Ну, FindAll копирует совпадающие элементы в новый список, тогда как Где просто возвращает лениво вычисленную последовательность - никакого копирования не требуется.

Поэтому я ожидаю, что Где будет немного быстрее, чем FindAll , даже когда результирующая последовательность будет полностью вычислена - и, конечно же, стратегия ленивого вычисления Где означает что если вы посмотрите только (скажем) на первое совпадение, вам не нужно будет проверять оставшуюся часть списка. (Как указывает Мэтью, ведется работа по поддержке конечного автомата для , где . Однако это будет иметь только фиксированную стоимость памяти - тогда как создание нового списка может потребовать выделения нескольких массивов и т. Д.)

В основном , FindAll (предикат) ближе к Где (предикат) .ToList () , чем к просто Где (предикат) .

Просто чтобы немного больше отреагировать на ответ Мэтью, я не думаю, что он проверил его достаточно тщательно. Его предикат выбирает половину элементов. Вот короткая, но полная программа, которая проверяет один и тот же список, но с тремя разными предикатами: один не выбирает элементов, один выбирает все элементы, а один выбирает половину из них. В каждом случае я запускаю тест пятьдесят раз, чтобы увеличить время.

Я использую Count () , чтобы убедиться, что результат Где полностью вычислен. Результаты показывают, что, если собрать около половины результатов, это двое - шея и шея. Не собирая результатов, FindAll побеждает. Собирая всех результатов, Где побеждает. Я нахожу это интригующим: все решения становятся медленнее по мере нахождения все большего и большего количества совпадений: FindAll требует большего копирования, а Где должен возвращать согласованные значения вместо простого цикла в реализации 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);
    }
}
52
ответ дан 27 November 2019 в 18:03
поделиться

Как насчет того, чтобы проверить, а не догадываться? Стыдно видеть неправильный ответ.

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

Изменить:

Даже если я переверну приведенный выше код с

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

Редактировать 2.0 ...

Если вам нужен список из подмножества текущего списка, самый быстрый метод, если FindAll (). Причина этого проста. Метод экземпляра FindAll использует индексатор в текущем списке вместо конечного автомата перечислителя. Метод расширения Where () - это внешний вызов другого класса, который использует перечислитель. Если вы перейдете от каждого узла в списке к следующему узлу, вам придется вызвать метод MoveNext () под крышками. Как видно из приведенных выше примеров, еще быстрее использовать записи индекса для создания нового списка (который указывает на исходные элементы, поэтому раздувание памяти будет минимальным), чтобы даже просто подсчитать количество отфильтрованных элементов.

Теперь, если вы собираетесь преждевременно выйти из Enumerator, метод Where () может быть быстрее. Конечно, если вы переместите логику раннего прерывания в предикат метода FindAll (), вы снова будете использовать индексатор вместо перечислителя.

Теперь есть другие причины для использования оператора Where () (например, другие методы linq, блоки foreach и многое другое), но вопрос был в том, что FindAll () быстрее, чем Where (). И если вы не выполните Where (), ответ будет положительным. (При сравнении яблок с яблоками)

Я не говорю, что не используйте LINQ или метод .Where (). Они делают код, который намного проще читать. Вопрос был в производительности, а не в том, насколько легко вы можете читать и понимать код. Быстро самый быстрый способ выполнить эту работу - использовать для пошагового выполнения каждого индекса и выполнения любой логики по своему усмотрению (даже при раннем выходе). Причина, по которой LINQ так хорош, заключается в сложных деревьях выражений и преобразованиях, которые вы можете получить с их помощью. Но использование итератора из метода .Where () должно пройти через тонны кода, чтобы найти путь к машине состояний в памяти, которая просто получает следующий индекс из списка. Также следует отметить, что этот метод .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
/*
14
ответ дан 27 November 2019 в 18:03
поделиться

Ну хоть померить можно.

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

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

2
ответ дан 27 November 2019 в 18:03
поделиться

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

1
ответ дан 27 November 2019 в 18:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: