Проблема производительности при использовании LINQ “везде”?

После обновления до ReSharper5 это дает мне еще более полезные подсказки относительно улучшений кода. Один я вижу везде, теперь подсказка для замены foreach-операторов запросами LINQ. Возьмите этот пример:

private Ninja FindNinjaById(int ninjaId)
{
    foreach (var ninja in Ninjas)
    {
        if (ninja.Id == ninjaId)
            return ninja;
    }
    return null;
}

Это предлагается замененное следующим использованием LINQ:

private Ninja FindNinjaById(int ninjaId)
{
    return Ninjas.FirstOrDefault(ninja => ninja.Id == ninjaId);
}

Это смотрит весь штраф, и я уверен, что это не проблема относительно производительности для замены этого foreach. Но это - что-то, что я должен сделать в целом? Или я мог бы столкнуться с проблемами производительности со всеми этими запросами LINQ везде?

18
задан stiank81 20 April 2010 в 11:24
поделиться

9 ответов

Вам нужно понять, что LINQ-запрос будет делать «под капотом», и сравнить это с запуском вашего кода, прежде чем вы узнаете, следует ли вам изменить Это. Как правило, я не имею в виду, что вам нужно знать точный код, который будет сгенерирован, но вам нужно знать основное представление о том, как это будет происходить при выполнении операции. В вашем примере я бы предположил, что LINQ в основном будет работать примерно так же, как ваш код, и, поскольку оператор LINQ более компактный и описательный, я бы предпочел его. Однако бывают случаи, когда LINQ не может быть идеальным выбором, хотя, вероятно, не так много. Обычно я думаю, что практически любую конструкцию цикла можно заменить эквивалентной конструкцией LINQ.

18
ответ дан 30 November 2019 в 05:54
поделиться

Вышеупомянутое делает то же самое.

Если вы правильно используете запросы LINQ, у вас не будет проблем с производительностью. Если вы используете его правильно, скорее всего, он будет быстрее из-за навыков людей, создающих LINQ.

Единственное, что вы можете получить от создания собственного, - это если вам нужен полный контроль, или LINQ не предлагает то, что вам нужно, или вам нужна лучшая возможность отладки.

3
ответ дан 30 November 2019 в 05:54
поделиться

Позвольте мне Начнем с того, что мне нравится LINQ за его выразительность и я использую его все время без каких-либо проблем.

Однако есть некоторые различия в производительности. Обычно они достаточно малы, чтобы их можно было игнорировать, но на критическом пути вашего приложения могут быть моменты, когда вы захотите их оптимизировать.

Вот набор различий, о которых вам следует знать и которые могут иметь значение для производительности:

  • LINQ чрезмерно использует вызовы делегатов, а вызовы делегатов выполняются (совсем немного) медленнее, чем вызовы методов, и, конечно, медленнее, чем встроенный код.
  • Делегат - это указатель на метод внутри объекта. Этот объект нужно создать.
  • Операторы LINQ обычно возвращают новый объект (итератор), который позволяет перебирать коллекцию в цикле. Таким образом, связанные операторы LINQ создают несколько новых объектов.
  • Когда ваш внутренний цикл использует объекты извне (называемые замыканиями), они также должны быть заключены в объекты (которые необходимо создать).
  • Многие операторы LINQ вызывают метод GetEnumerator для коллекции, чтобы выполнить ее итерацию. Вызов GetEnumerator обычно обеспечивает создание еще одного объекта.
  • Итерация коллекции выполняется с помощью интерфейса IEnumerator . Вызовы интерфейса немного медленнее, чем вызовы обычных методов.
  • Объекты IEnumerator часто необходимо удалить или, по крайней мере, вызвать Dispose .

Если производительность важна, попробуйте также использовать для вместо foreach .

Опять же, мне нравится LINQ, и я не припомню, чтобы когда-либо решал не использовать запрос LINQ (к объектам) из-за производительности. Итак, не выполняйте никаких преждевременных оптимизаций . Сначала начните с наиболее удобочитаемого решения, а затем оптимизируйте его при необходимости. Итак, профиль, профиль и профиль .

15
ответ дан 30 November 2019 в 05:54
поделиться

Одна вещь, которую мы определили как проблему с производительностью, - это создание большого количества лямбда-выражений и повторение небольших коллекций. Что происходит в преобразованном образце?

Ninjas.FirstOrDefault(ninja => ninja.Id == ninjaId)

Сначала создается новый экземпляр (сгенерированного) типа замыкания. Новый экземпляр в управляемой куче, некоторая работа для GC. Во-вторых, новый экземпляр делегата создается из метода в этом закрытии. Затем вызывается метод FirstOrDefault. Что он делает? Он выполняет итерацию коллекции (так же, как ваш исходный код) и вызывает делегат.

Итак, в основном у вас есть 4 вещи, добавленные сюда: 1. Создать закрытие 2. Создать делегата 3. Позвонить через делегата 4. Собирать замыкание и делегировать

Если вы вызываете FindNinjaById много раз, вы добавляете это, что может быть важным попаданием перфорации. Конечно, измерить.

Если вы замените его на (эквивалент)

Ninjas.Where(ninja => ninja.Id == ninjaId).FirstOrDefault()

, добавится 5. Создание конечного автомата для итератора («Где» - это функция выдачи)

7
ответ дан 30 November 2019 в 05:54
поделиться

Анекдот: когда я только знакомился с C # 3.0 и LINQ, я все еще находился в фазе «когда есть молоток, все выглядит как гвоздь». В качестве школьного задания я должен был написать игру «Подключи четыре / четыре в ряд» как упражнение по алгоритмам состязательного поиска. Я использовал LINQ на протяжении всей программы. В одном конкретном случае мне нужно было найти строку, в которую приземлится игровая фишка, если я уроню ее в определенный столбец. Идеальный вариант использования для запроса LINQ! Это оказалось очень медленно. Однако проблема заключалась не в LINQ, а в том, что я сначала искал . Я оптимизировал это, просто сохранив справочную таблицу: целочисленный массив, содержащий номер строки для каждого столбца игровой доски, обновляя эту таблицу при вставке игровой части. Излишне говорить, что это было намного быстрее.

Извлеченный урок: сначала оптимизируйте свой алгоритм, и конструкции высокого уровня, такие как LINQ, действительно могут облегчить это.

Тем не менее, создание всех этих делегатов требует определенных затрат. С другой стороны, использование ленивого характера LINQ также может дать выигрыш в производительности. Если вы вручную перебираете коллекцию, вы в значительной степени вынуждены создавать промежуточные List <> , тогда как с LINQ вы в основном выполняете потоковую передачу результатов.

5
ответ дан 30 November 2019 в 05:54
поделиться

Мы создали огромные приложения с обильным использованием LINQ. Это никогда, никогда не замедляло нас.

Вполне возможно написать запросы LINQ, которые будут очень медленными, но легче исправить простые операторы LINQ, чем огромные алгоритмы для / если / для / возврата.

Прислушайтесь к совету resharper :)

5
ответ дан 30 November 2019 в 05:54
поделиться

Замечательная особенность запросов LINQ заключается в том, что они делают его предельно простым для преобразования в параллельный запрос. В зависимости от того, что вы делаете, он может быть или не быть быстрее (как всегда, профиль), но, тем не менее, довольно аккуратно.

3
ответ дан 30 November 2019 в 05:54
поделиться

Профиль


Единственный способ узнать наверняка - это профилировать. Да, некоторые запросы могут выполняться медленнее. Но если вы посмотрите на то, что ReSharper заменил здесь, это, по сути, то же самое, но сделано по-другому. Ниндзя зациклены, каждый идентификатор проверяется. Во всяком случае, вы можете утверждать, что этот рефакторинг сводится к удобочитаемости. Какой из двух вам легче читать?

Большие наборы данных, конечно, будут иметь большее влияние, но, как я уже сказал, профиль. Это единственный способ убедиться, что такие улучшения имеют негативный эффект.

6
ответ дан 30 November 2019 в 05:54
поделиться

Добавлю свой собственный опыт использования LINQ там, где производительность действительно имеет значение - с Monotouch - разница все еще незначительна.

На 3GS iPhone у вас есть «ограниченные возможности» около 46 Мб оперативной памяти и процессора ARM с тактовой частотой 620 МГц. По общему признанию, код скомпилирован AOT, но даже на симуляторе, где он JIT'd и проходит через длинную серию косвенных обращений, разница составляет десятые доли миллисекунды для наборов из 1000 объектов.

Наряду с Windows Mobile здесь вам нужно беспокоиться о затратах на производительность - не в огромных приложениях ASP.NET, которые работают на четырехъядерных серверах 8 Гбайт, или настольных компьютерах с двойной оценкой. Единственным исключением могут быть большие наборы объектов, хотя, возможно, вы все равно будете выполнять ленивую загрузку, и задача начального запроса будет выполняться на сервере базы данных.

Это своего рода клише для Stackoverflow, но используйте более короткий и читаемый код до тех пор, пока 100 миллисекунд действительно не имеют значения.

3
ответ дан 30 November 2019 в 05:54
поделиться
Другие вопросы по тегам:

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