Аллокации, которых нет в коде: охота на скрытый боксинг в .NET 10
Csharponelove 36 минут назад Аллокации, которых нет в коде: охота на скрытый боксинг в .NET 10 10 мин 1.3K .NET * C# * Высоконагруженные системы * Программирование * Самая дорогая аллокация в вашем сервисе та, которой...
Anthropic — What company has the best second artificial intelligence model at the end of June?
Значимый прорыв формирует отрасль ИИ: Csharponelove 36 минут назад Аллокации, которых нет в коде: охота на скрытый боксинг в . NET * C# * Высоконагруженные системы * Программирование * Самая дорогая аллокация в вашем сервисе та, которой нет в исходниках. Вы написали struct ради zero-allocation, прошли code review, а в проде Gen0-коллекции все равно идут косяком.
Потому что между вашим кодом и машинным кодом стоит компилятор, и он молча упаковывает ваш value-тип в кучу там, где вы этого не просили — а на код-ревью этого не видно. NET — это не только object o = 42. Он прячется в вызовах интерфейсных методов на struct, в дефолтном ValueType.
Технические детали
Equals, в params object-аргументах, в foreach по интерфейсу и в замыканиях. При этом часть “классических” примеров боксинга из старых гайдов на современном рантайме уже не аллоцирует — JIT научился их вырезать, и слепо копировать советы десятилетней давности вредно. Ниже — карта мест, где боксинг живёт и сейчас, отдельный разбор того, что рантайм уже оптимизировал, реальный мини-кейс, воспроизводимый бенчмарк на BenchmarkDotNet с MemoryDiagnoser, способ ловить упаковку через DOTNET_JitDisasm и dotnet-gcdump, и паттерны лечения без потери читаемости.
NET 10 (текущий LTS) и C# 13/14-уровне компилятора, Release, без отладчика, BenchmarkDotNet с MemoryDiagnoser. NET 8/9 поведение в основном такое же, но отдельные оптимизации JIT отличаются между мажорными версиями — поэтому главный принцип статьи: не верьте на слово (в том числе мне), гоняйте MemoryDiagnoser на своей версии рантайма. Числа в таблицах ниже — иллюстративные, порядок величины, а не точные замеры с вашего железа.
Пролог: “у нас же всё на struct, откуда Gen0? ”Сервис на горячем пути считает метрики: миллионы маленьких readonly struct-значений в секунду, никакого new, никаких классов в hot path. По задумке — ноль аллокаций.
Отраслевые последствия
На дашборде — стабильный поток Gen0-коллекций раз в несколько секунд под нагрузкой. Профайлер показывает аллокации, но стек ведёт в метод, где в коде нет ни одного new. Там цикл по интерфейсу, пара вызовов .
Equals(), передача значения в params-метод лога. В машинном коде — box-инструкции на каждой итерации. Это и есть скрытый боксинг: компилятор C# и JIT упаковывают ваш struct в объект на куче, потому что в конкретной точке кода value-тип нужно представить как ссылочный.
Симптом — Gen0-коллекции “из ниоткуда”, и его не видно ни в code review, ни в дампе, пока не посмотришь на IL или дизасм. Если тема близка - я регулярно разбираю такие штуки по C# и . NET (внутренности рантайма, перформанс, неочевидные грабли с замерами и дизасмом) в своём Telegram-канале: t.
Событие, по словам экспертов, усилит конкуренцию в сфере ИИ.




