Забота о производительности при использовании LINQ "везде"? - PullRequest
18 голосов
/ 20 апреля 2010

После обновления до 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 везде?

Ответы [ 9 ]

18 голосов
/ 20 апреля 2010

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

15 голосов
/ 20 апреля 2010

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

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

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

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

Когда речь идет о производительности, попробуйте также использовать for сверх foreach.

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

7 голосов
/ 20 апреля 2010

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

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

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

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

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

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

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

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

6 голосов
/ 20 апреля 2010

Профиль


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

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

5 голосов
/ 21 апреля 2010

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

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

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

5 голосов
/ 20 апреля 2010

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

Совершенно возможно писать запросы LINQ, которые будут очень медленными, но проще исправить простые операторы LINQ, чем огромные для алгоритмов / if / for / return.

Примите совет Решарпера:)

3 голосов
/ 29 апреля 2010

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

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

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

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

3 голосов
/ 20 апреля 2010

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

3 голосов
/ 20 апреля 2010

Выше делает то же самое.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...