Сейчас я работаю над статьёй о том, как писать производительный код для работы с коллекциями. В ней будут как базовые советы для начинающих программистов, так и продвинутые. Поскольку статья ещё не готова, держите мем и фрагмент, посвящённый LINQ.
---
В большинстве случаев методы LINQ работают медленнее и используют больше памяти. Это не значит, что LINQ – это плохо. Нет, это очень крутая фича C# и, естественно, я её тоже использую. Однако в некоторых и, подчеркну, редких ситуациях для лучшей производительности стоит писать в императивном стиле. Рассмотрим несколько простых примеров.
Select vs ConvertAll vs императивный стиль
Предположим, у нас есть массив транзакций:
public record class Transaction(
Guid Id,
int Amount,
string Description);
Допустим, нужно получить поле Description для всех элементов массива _transactions. Неважно, зачем нам это, — просто нужно. 🙂 Это можно сделать с помощью Array.ConvertAll, методов Select и ToArray, или вручную. Сравним эти три способа.
// Benchmark 1
Array.ConvertAll(_transactions,
x => x.Description);
// Benchmark 2
_transactions
.Select(x => x.Description)
.ToArray();
// Benchamrk 3
var array = new string[_transactions.Length];
for (int i = 0; i < _transactions.Length; i++) {
array[i] = _transactions[i].Description;
}
// Results
| Method | Mean | Ratio |
|------------------ |-----------:|---------:|
| ArrayConvertAll | 1,296.4 μs | -3% |
| LinqSelect | 1,343.4 μs | baseline |
| ImperativeConvert | 1,250.4 μs | -7% |
Array.ConvertAll работает на 3% быстрее, чем LINQ, а императивный стиль — на 7% быстрее. Да, разница небольшая, но методы LINQ часто используются последовательно, и в итоге суммарное отличие в производительности может оказаться более заметным.
Any vs Exists vs императивный стиль
Рассмотрим второй пример — проверка существования элемента в массиве.
// Benchmark 1
return Array.Exists(_transactions,
x => x.Amount > 1_000_00);
// Benchmark 2
return _transactions
.Any(x => x.Amount > 1_000_00);
// Benchmark 3
foreach (var t in _transactions) {
if (t.Amount > 1_000_000) return true;
}
return false;
// Results
| Method | Mean | Ratio |
|------------------ |-----------:|---------:|
| ArrayExists | 567.9 μs | -39% |
| LinqAny | 926.1 μs | baseline |
| ImperativeExists | 488.5 μs | -47% |
Array.Exists работает на 39% быстрее, чем LINQ, а императивный стиль — почти вдвое быстрее.
---
Я обязательно проверю и другие методы, а также протестирую с массивами разного размера. Но что-то мне подсказывает, что этот раздел будет называться «Избегайте LINQ».
Кстати, напишите в комментариях, с какими проблемами производительности вам приходилось сталкиваться при работе с коллекциями. Если этого ещё нет в статье, я проанализирую, сделаю бенчмарки и включу разбор в статью.
