Одна из самых полезных оптимизаций в .NET 10 работает незаметно. JIT проверяет, может ли объект пережить метод, в котором его создали. Если не может, объект размещается на стеке, а не в куче. Сборщику мусора отслеживать нечего, а сам объект можно разложить на отдельные поля и оптимизировать дальше.
Раньше это работало только для простых случаев. В .NET 10 на стек попадают ещё четыре вида объектов.
Маленькие массивы фиксированного размера, причём и из значимых типов, и из ссылочных:
int[] numbers = { 1, 2, 3 }; // теперь на стеке
string[] words = { "Hello", "World!" }; // тоже на стеке
Делегаты. Если лямбда не выходит за пределы метода, объект
Func больше не уходит в кучу. Замыкание пока ещё аллоцируется, но это обещают доделать.Объекты в полях локальных структур. Если массив лежит в поле структуры, а структура не покидает метод, массив тоже размещается на стеке. В ассемблере пропадает вызов аллокатора.
Заодно в JIT добавили девиртуализацию интерфейсов массивов, поэтому
foreach по IEnumerable<T> над массивом теперь инлайнится. Ещё улучшили loop inversion и раскладку кода, а на Arm64 пауз GC стало меньше на 8–20%.Переписывать ничего не нужно: привычный код с лямбдами и маленькими массивами просто начинает аллоцировать меньше после перехода на .NET 10.
https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-10/runtime#escape-analysis
