После обновления до 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 везде?
Вам нужно понять, что LINQ-запрос будет делать «под капотом», и сравнить это с запуском вашего кода, прежде чем вы узнаете, следует ли вам изменить Это. Как правило, я не имею в виду, что вам нужно знать точный код, который будет сгенерирован, но вам нужно знать основное представление о том, как это будет происходить при выполнении операции. В вашем примере я бы предположил, что LINQ в основном будет работать примерно так же, как ваш код, и, поскольку оператор LINQ более компактный и описательный, я бы предпочел его. Однако бывают случаи, когда LINQ не может быть идеальным выбором, хотя, вероятно, не так много. Обычно я думаю, что практически любую конструкцию цикла можно заменить эквивалентной конструкцией LINQ.
Вышеупомянутое делает то же самое.
Если вы правильно используете запросы LINQ, у вас не будет проблем с производительностью. Если вы используете его правильно, скорее всего, он будет быстрее из-за навыков людей, создающих LINQ.
Единственное, что вы можете получить от создания собственного, - это если вам нужен полный контроль, или LINQ не предлагает то, что вам нужно, или вам нужна лучшая возможность отладки.
Позвольте мне Начнем с того, что мне нравится LINQ за его выразительность и я использую его все время без каких-либо проблем.
Однако есть некоторые различия в производительности. Обычно они достаточно малы, чтобы их можно было игнорировать, но на критическом пути вашего приложения могут быть моменты, когда вы захотите их оптимизировать.
Вот набор различий, о которых вам следует знать и которые могут иметь значение для производительности:
GetEnumerator
для коллекции, чтобы выполнить ее итерацию. Вызов GetEnumerator
обычно обеспечивает создание еще одного объекта. IEnumerator
. Вызовы интерфейса немного медленнее, чем вызовы обычных методов. Объекты IEnumerator
часто необходимо удалить или, по крайней мере, вызвать Dispose
. Если производительность важна, попробуйте также использовать для
вместо foreach
.
Опять же, мне нравится LINQ, и я не припомню, чтобы когда-либо решал не использовать запрос LINQ (к объектам) из-за производительности. Итак, не выполняйте никаких преждевременных оптимизаций . Сначала начните с наиболее удобочитаемого решения, а затем оптимизируйте его при необходимости. Итак, профиль, профиль и профиль .
Одна вещь, которую мы определили как проблему с производительностью, - это создание большого количества лямбда-выражений и повторение небольших коллекций. Что происходит в преобразованном образце?
Ninjas.FirstOrDefault(ninja => ninja.Id == ninjaId)
Сначала создается новый экземпляр (сгенерированного) типа замыкания. Новый экземпляр в управляемой куче, некоторая работа для GC. Во-вторых, новый экземпляр делегата создается из метода в этом закрытии. Затем вызывается метод FirstOrDefault. Что он делает? Он выполняет итерацию коллекции (так же, как ваш исходный код) и вызывает делегат.
Итак, в основном у вас есть 4 вещи, добавленные сюда: 1. Создать закрытие 2. Создать делегата 3. Позвонить через делегата 4. Собирать замыкание и делегировать
Если вы вызываете FindNinjaById много раз, вы добавляете это, что может быть важным попаданием перфорации. Конечно, измерить.
Если вы замените его на (эквивалент)
Ninjas.Where(ninja => ninja.Id == ninjaId).FirstOrDefault()
, добавится 5. Создание конечного автомата для итератора («Где» - это функция выдачи)
Анекдот: когда я только знакомился с C # 3.0 и LINQ, я все еще находился в фазе «когда есть молоток, все выглядит как гвоздь». В качестве школьного задания я должен был написать игру «Подключи четыре / четыре в ряд» как упражнение по алгоритмам состязательного поиска. Я использовал LINQ на протяжении всей программы. В одном конкретном случае мне нужно было найти строку, в которую приземлится игровая фишка, если я уроню ее в определенный столбец. Идеальный вариант использования для запроса LINQ! Это оказалось очень медленно. Однако проблема заключалась не в LINQ, а в том, что я сначала искал . Я оптимизировал это, просто сохранив справочную таблицу: целочисленный массив, содержащий номер строки для каждого столбца игровой доски, обновляя эту таблицу при вставке игровой части. Излишне говорить, что это было намного быстрее.
Извлеченный урок: сначала оптимизируйте свой алгоритм, и конструкции высокого уровня, такие как LINQ, действительно могут облегчить это.
Тем не менее, создание всех этих делегатов требует определенных затрат. С другой стороны, использование ленивого характера LINQ также может дать выигрыш в производительности. Если вы вручную перебираете коллекцию, вы в значительной степени вынуждены создавать промежуточные List <>
, тогда как с LINQ вы в основном выполняете потоковую передачу результатов.
Мы создали огромные приложения с обильным использованием LINQ. Это никогда, никогда не замедляло нас.
Вполне возможно написать запросы LINQ, которые будут очень медленными, но легче исправить простые операторы LINQ, чем огромные алгоритмы для / если / для / возврата.
Прислушайтесь к совету resharper :)
Замечательная особенность запросов LINQ заключается в том, что они делают его предельно простым для преобразования в параллельный запрос. В зависимости от того, что вы делаете, он может быть или не быть быстрее (как всегда, профиль), но, тем не менее, довольно аккуратно.
Единственный способ узнать наверняка - это профилировать. Да, некоторые запросы могут выполняться медленнее. Но если вы посмотрите на то, что ReSharper заменил здесь, это, по сути, то же самое, но сделано по-другому. Ниндзя зациклены, каждый идентификатор проверяется. Во всяком случае, вы можете утверждать, что этот рефакторинг сводится к удобочитаемости. Какой из двух вам легче читать?
Большие наборы данных, конечно, будут иметь большее влияние, но, как я уже сказал, профиль. Это единственный способ убедиться, что такие улучшения имеют негативный эффект.
Добавлю свой собственный опыт использования LINQ там, где производительность действительно имеет значение - с Monotouch - разница все еще незначительна.
На 3GS iPhone у вас есть «ограниченные возможности» около 46 Мб оперативной памяти и процессора ARM с тактовой частотой 620 МГц. По общему признанию, код скомпилирован AOT, но даже на симуляторе, где он JIT'd и проходит через длинную серию косвенных обращений, разница составляет десятые доли миллисекунды для наборов из 1000 объектов.
Наряду с Windows Mobile здесь вам нужно беспокоиться о затратах на производительность - не в огромных приложениях ASP.NET, которые работают на четырехъядерных серверах 8 Гбайт, или настольных компьютерах с двойной оценкой. Единственным исключением могут быть большие наборы объектов, хотя, возможно, вы все равно будете выполнять ленивую загрузку, и задача начального запроса будет выполняться на сервере базы данных.
Это своего рода клише для Stackoverflow, но используйте более короткий и читаемый код до тех пор, пока 100 миллисекунд действительно не имеют значения.