Сообщение Повышение производительности в .NET 10 от 07.10.2025 9:32
Изменено 09.10.2025 12:25 VladD2
На русском
JIT
Среди всех областей .NET наиболее значимым является JIT-компилятор (Just-In-Time, «точно в срок»). Каждое приложение .NET, будь то небольшой консольный инструмент или крупномасштабная корпоративная служба, в конечном счёте использует JIT-компилятор для преобразования кода на промежуточном языке (IL) в оптимизированный машинный код. Любое улучшение качества кода, генерируемого JIT-компилятором, оказывает комплексное воздействие, повышая производительность всей экосистемы без необходимости вносить изменения в собственный код или даже перекомпилировать C#. И в .NET 10 таких улучшений предостаточно.
Деабстракция
Как и во многих других языках, в .NET исторически наблюдался «штраф за абстракцию» — дополнительные выделения памяти и косвенная адресация, которые могут возникать при использовании высокоуровневых языковых функций, таких как интерфейсы, итераторы и делегаты. С каждым годом JIT-компилятор всё лучше оптимизирует уровни абстракции, позволяя разработчикам писать простой код и при этом получать высокую производительность. .NET 10 продолжает эту традицию. В результате идиоматический C# (с использованием интерфейсов, циклов foreach , лямбда-выражений и т. д.) работает почти так же быстро, как тщательно продуманный и отлаженный вручную код.
Выделение стека объектов
Одним из самых интересных направлений в области деабстракции в .NET 10 является расширенное использование анализа выхода за пределы метода для выделения объектов в стеке. Анализ выхода за пределы метода — это метод компилятора, который позволяет определить, выходит ли объект, выделенный в методе, за пределы этого метода, то есть доступен ли этот объект после возврата из метода (например, сохранён ли он в поле или возвращён вызывающей стороне) или используется каким-то образом, который среда выполнения не может отследить в рамках метода (например, передан неизвестному вызываемому объекту). Если компилятор может доказать, что объект не выйдет за пределы метода, то время жизни этого объекта будет ограничено методом, и его можно будет выделить в стеке, а не в куче. Выделение в стеке обходится гораздо дешевле (простое увеличение указателя для выделения и автоматическое освобождение при выходе из метода) и снижает нагрузку на сборщик мусора, поскольку объект не нужно отслеживать сборщику мусора. В .NET 9 уже была реализована ограниченная поддержка анализа выхода за пределы метода и выделения в стеке; в .NET 10 эта поддержка значительно расширена.
public partial class Tests
{
[Benchmark]
[Arguments(42)]
public int Sum(int y)
{
Func<int, int> addY = x => x + y;
return DoubleResult(addY, y);
}
private int DoubleResult(Func<int, int> func, int arg)
{
int result = func(arg);
return result + result;
}
}If we just run this benchmark and compare .NET 9 and .NET 10, we can immediately tell something interesting is happening.
Method Runtime Mean Ratio Code Size Allocated Alloc Ratio
Sum .NET 9.0 19.530 ns 1.00 118 B 88 B 1.00
Sum .NET 10.0 6.685 ns 0.34 32 B 24 B 0.27
На русском
JIT
Среди всех областей .NET наиболее значимым является JIT-компилятор (Just-In-Time, «точно в срок»). Каждое приложение .NET, будь то небольшой консольный инструмент или крупномасштабная корпоративная служба, в конечном счёте использует JIT-компилятор для преобразования кода на промежуточном языке (IL) в оптимизированный машинный код. Любое улучшение качества кода, генерируемого JIT-компилятором, оказывает комплексное воздействие, повышая производительность всей экосистемы без необходимости вносить изменения в собственный код или даже перекомпилировать C#. И в .NET 10 таких улучшений предостаточно.
Деабстракция
Как и во многих других языках, в .NET исторически наблюдался «штраф за абстракцию» — дополнительные выделения памяти и косвенная адресация, которые могут возникать при использовании высокоуровневых языковых функций, таких как интерфейсы, итераторы и делегаты. С каждым годом JIT-компилятор всё лучше оптимизирует уровни абстракции, позволяя разработчикам писать простой код и при этом получать высокую производительность. .NET 10 продолжает эту традицию. В результате идиоматический C# (с использованием интерфейсов, циклов foreach , лямбда-выражений и т. д.) работает почти так же быстро, как тщательно продуманный и отлаженный вручную код.
Выделение стека объектов
Одним из самых интересных направлений в области деабстракции в .NET 10 является расширенное использование анализа выхода за пределы метода для выделения объектов в стеке. Анализ выхода за пределы метода — это метод компилятора, который позволяет определить, выходит ли объект, выделенный в методе, за пределы этого метода, то есть доступен ли этот объект после возврата из метода (например, сохранён ли он в поле или возвращён вызывающей стороне) или используется каким-то образом, который среда выполнения не может отследить в рамках метода (например, передан неизвестному вызываемому объекту). Если компилятор может доказать, что объект не выйдет за пределы метода, то время жизни этого объекта будет ограничено методом, и его можно будет выделить в стеке, а не в куче. Выделение в стеке обходится гораздо дешевле (простое увеличение указателя для выделения и автоматическое освобождение при выходе из метода) и снижает нагрузку на сборщик мусора, поскольку объект не нужно отслеживать сборщику мусора. В .NET 9 уже была реализована ограниченная поддержка анализа выхода за пределы метода и выделения в стеке; в .NET 10 эта поддержка значительно расширена.
public partial class Tests
{
[Benchmark]
[Arguments(42)]
public int Sum(int y)
{
Func<int, int> addY = x => x + y;
return DoubleResult(addY, y);
}
private int DoubleResult(Func<int, int> func, int arg)
{
int result = func(arg);
return result + result;
}
}If we just run this benchmark and compare .NET 9 and .NET 10, we can immediately tell something interesting is happening.
Method Runtime Mean Ratio Code Size Allocated Alloc Ratio Sum .NET 9.0 19.530 ns 1.00 118 B 88 B 1.00 Sum .NET 10.0 6.685 ns 0.34 32 B 24 B 0.27