Аллокации и зеро-аллокационный код
↑ 7.1 Производительность .NET и кэширование · ← Предыдущая · → Следующая
Зачем это на собесе
Про низкоуровневую производительность:
Span<T>, пулы,structи когда это оправдано.
Объяснение
Каждая аллокация в куче нагружает GC. В горячих путях (парсеры, сериализация, middleware) снижают число аллокаций.
| Приём | Эффект |
|---|---|
Span<T>/ReadOnlySpan<T>/Memory<T> | срезы без копирования массивов и подстрок |
stackalloc | небольшие буферы на стеке |
ArrayPool<T>.Shared | переиспользование больших массивов |
ObjectPool<T>, StringBuilder из пула | переиспользование объектов |
struct/readonly struct/ref struct | значения без кучи (осторожно с копированием) |
ValueTask | без аллокации при синхронном завершении (см. Время жизни: Singleton, Scoped, Transient) |
string.Create, интерполяция в DefaultInterpolatedStringHandler | меньше промежуточных строк |
| Избежание замыканий/боксинга/LINQ в горячих путях | нет скрытых аллокаций |
static лямбды | нет захвата контекста |
FrozenDictionary/SearchValues | быстрые неизменяемые структуры |
// Разбор строки без Substring
static int SumCsv(ReadOnlySpan<char> line)
{
int sum = 0;
foreach (var range in line.Split(',')) // .NET 9: MemoryExtensions.Split
sum += int.Parse(line[range]);
return sum;
}
var buffer = ArrayPool<byte>.Shared.Rent(64 * 1024);
try { /* работа */ } finally { ArrayPool<byte>.Shared.Return(buffer); }Нюансы и подводные камни
- Оптимизация оправдана только по профилю: читаемость дороже.
- Массив из пула может быть больше запрошенного и не очищен.
Span<T>нельзя хранить в поле класса и использовать вasync.- Возврат буфера в пул дважды или использование после возврата приводит к порче данных.
- Большие объекты (≥85 КБ) попадают в LOH и собираются только при gen2.
Практика
- Уберите аллокации в парсере строк через
Spanи сравните в BenchmarkDotNet. - Замените выделение буферов на
ArrayPool. - Найдите скрытые аллокации через
dotnet-trace/allocation profiler.
Вопросы с ответами
Зачем Span<T>?
Позволяет работать со срезами памяти (массив, стек, нативная память) без копирования и аллокаций.
Когда ArrayPool оправдан?
Для временных больших буферов в горячем пути, чтобы не нагружать LOH и GC.