ReadyToRun и многоуровневая компиляция
Разберитесь в предварительной компиляции ReadyToRun, многоуровневой JIT-компиляции и PGO (оптимизации на основе профиля) в .NET.
«ReadyToRun и многоуровневая компиляция» — бесплатный урок C# Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.
JIT, R2R и многоуровневая компиляция
.NET использует несколько стратегий компиляции, чтобы сбалансировать скорость запуска и максимальную пропускную способность. Понимание JIT, ReadyToRun и многоуровневой компиляции поможет выбрать подходящие параметры для вашей рабочей нагрузки.
Как работает JIT
JIT-компилятор преобразует байт-код IL в нативный машинный код при первом вызове каждого метода. Это увеличивает задержку первого вызова, но создает высокооптимизированный код для выполняющей машины.
// What happens at runtime:
// 1. App starts — methods are stubs pointing to the JIT
// 2. First call to MyMethod() → JIT compiles IL → native x64 code
// 3. Subsequent calls → native code runs directly (no JIT overhead)
// Startup cost: each method's first call takes microseconds for JIT
// Peak performance: excellent — JIT knows the actual CPU features
// .NET 9 JIT optimizations applied automatically:
// - Inlining
// - Loop unrolling
// - Escape analysis
// - SIMD vectorization
// - Profile-Guided Optimization (PGO)Компиляция ReadyToRun (R2R)
ReadyToRun (R2R) предварительно компилирует сборки в нативный код во время публикации. Приложение запускается быстрее, поскольку работа JIT уже выполнена, но размер файлов на диске увеличивается.
// Enable R2R in .csproj:
<PropertyGroup>
<PublishReadyToRun>true</PublishReadyToRun>
<!-- Optional: composite R2R — whole app in one native image -->
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
</PropertyGroup>
// Publish:
dotnet publish -c Release -r linux-x64 --self-contained
// Benefits:
// - Startup time improvement: ~20-30% faster than JIT-only
// - No runtime required (self-contained)
// Trade-off:
// - Larger binary (IL + R2R code both stored)
// - Publish takes longer (cross-gen2 runs at publish time)R2R и Native AOT
R2R по-прежнему поставляет IL вместе с нативным кодом и требует среду выполнения .NET. Native AOT полностью удаляет IL и среду выполнения. Выбирайте подходящий вариант в зависимости от требований к развертыванию.
// ReadyToRun:
// ✓ Still ships IL (fallback JIT for dynamically-generated code)
// ✓ Supports full reflection
// ✓ .NET runtime required on target
// ✓ Startup: ~30% faster than JIT
// Size: medium (IL + native)
// Native AOT:
// ✓ No IL, no runtime needed on target
// ✓ Startup: ~36x faster than JIT
// ✗ No reflection (must use source generators)
// ✗ Trimming limitations
// Size: smallest
// JIT only:
// ✓ Full reflection, no limitations
// ✓ Best peak throughput via PGO
// ✗ Slowest startupМногоуровневая компиляция
Многоуровневая компиляция по умолчанию включена в .NET. Методы начинают работу на уровне 0 (быстрая компиляция), а часто используемые методы в фоновом режиме перекомпилируются на уровне 1 (полная оптимизация).
// Tiered compilation lifecycle:
// Tier 0: fast JIT with minimal optimization
// → method is callable in ~100 microseconds
// After ~30 calls (configurable): marked as "hot"
// Tier 1: full JIT with all optimizations + PGO
// → background thread re-compiles the method
// → future calls use optimized native code
// Usually transparent — you don't need to configure it
// Disable (rarely needed, only for debugging JIT issues):
// DOTNET_TieredCompilation=0
// Disable tiered PGO if benchmarking:
// DOTNET_TieredPGO=0Оптимизация на основе профиля (PGO)
В .NET 8+ динамическая PGO включена по умолчанию. Среда выполнения собирает данные профиля вызовов на уровне 0, а затем использует этот профиль для создания высокоспециализированного кода уровня 1 для часто выполняемых участков.
// PGO automatically specializes code like:
// Before PGO (generic virtual dispatch):
void Process(IAnimal animal) => animal.Speak();
// → runtime checks type on every call
// After PGO (type-specialized inline):
void Process(IAnimal animal)
{
if (animal is Dog) { /* inlined Dog.Speak() */ }
else if (animal is Cat){ /* inlined Cat.Speak() */ }
else animal.Speak(); // slow path
}
// Enabled by default in .NET 8+
// Verify:
// DOTNET_ReadyToRun=0
// DOTNET_TieredCompilation=1 (default)
// DOTNET_TieredPGO=1 (default .NET 8+)Хуки запуска
Хуки запуска (DOTNET_STARTUP_HOOKS) позволяют внедрять код до запуска Main. Это удобно для диагностики, переопределения конфигурации или инструментирования без изменения кода приложения.
// Create a hook class:
public class StartupHook
{
public static void Initialize()
{
Console.WriteLine("[StartupHook] Initializing before Main");
// e.g., register telemetry, inject config overrides
Environment.SetEnvironmentVariable("FEATURE_FLAG", "1");
}
}
// Compile to a DLL and set the env var:
export DOTNET_STARTUP_HOOKS=/path/to/MyHook.dll
// Then run your app normally:
dotnet run
// [StartupHook] Initializing before Main
// [Your App Output]Параметры конфигурации среды выполнения
Среда выполнения .NET предоставляет переменные окружения и параметры в runtimeconfig.json для настройки сборщика мусора, пула потоков и поведения JIT без изменения кода.
// runtimeconfig.template.json (baked into app at publish):
{
"configProperties": {
"System.GC.Server": true, // server GC (multi-core)
"System.GC.Concurrent": true, // background GC
"System.GC.HeapHardLimit": 536870912, // 512 MB hard limit
"System.Threading.ThreadPool.MinThreads": 20,
"System.Threading.ThreadPool.MaxThreads": 200,
"System.Runtime.TieredCompilation": true
}
}
// Environment variable overrides (useful in containers):
// DOTNET_GCHeapHardLimit=536870912
// DOTNET_ThreadPool_UnfairSemaphoreSpinLimit=70
// DOTNET_GCConserve=1 // reduce memory at cost of throughputCrossgen2 и составные образы
Crossgen2 — это инструмент, создающий образы R2R. Составной R2R предварительно компилирует все приложение (приложение + платформу) в единый общий нативный образ для максимально быстрого запуска.
// Composite R2R: compile app + framework into one native image
<PropertyGroup>
<PublishReadyToRun>true</PublishReadyToRun>
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
</PropertyGroup>
// Normal R2R includes multiple images per assembly
// Composite merges them → fewer file reads at startup
// Manual crossgen2 invocation:
dotnet tool install -g dotnet-crossgen2
crossgen2 \
--composite \
--out composite.dll \
MyApp.dll framework/System.Runtime.dll
// Inspect R2R content:
dotnet-ildasm --methodheader MyApp.dll | grep --color R2RПрактический пример: настройка запуска ASP.NET Core
Перечень практических параметров для максимального повышения производительности запуска службы ASP.NET Core в рабочей среде.
// .csproj:
<PublishReadyToRun>true</PublishReadyToRun> // pre-JIT at publish
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
<TieredCompilation>true</TieredCompilation> // default
// Program.cs — avoid startup work:
// ✓ Use AddHealthChecks() NOT AddDbContextCheck() for faster probe
// ✓ Lazy-initialize expensive singletons
// ✓ Don't await long tasks in Program.cs before app.Run()
// Container settings:
// DOTNET_GCServer=1 → server GC for multi-core pods
// DOTNET_GCHeapHardLimit=536870912 → prevent OOM in constrained pods
// DOTNET_ThreadPool_MinThreads=20 → reduce thread starvation under burst load
// Measure startup:
// app.MapGet("/startup-time",
// () => $"Ready in: {Stopwatch.GetElapsedTime(startTimestamp).TotalMs}ms");Быстрая проверка
В чем заключается ключевое различие между ReadyToRun (R2R) и Native AOT?
Итоги: ReadyToRun и многоуровневая компиляция
Основные выводы:
- Многоуровневая компиляция: используется по умолчанию в .NET — уровень 0 (быстрая компиляция) → уровень 1 (оптимизация + PGO)
- Динамическая PGO: используется по умолчанию в .NET 8+ — среда выполнения профилирует часто выполняемые участки и специализирует код уровня 1
- ReadyToRun: предварительно компилируется при публикации; запуск примерно на 30% быстрее; по-прежнему требует среду выполнения; поддерживает отражение
- Native AOT: не использует IL и среду выполнения; запуск примерно в 36 раз быстрее; не поддерживает отражение; двоичный файл имеет минимальный размер
- Настройка выполняется через
runtimeconfig.jsonи переменные окружения (параметры сборщика мусора, пула потоков и JIT) - Составной R2R объединяет приложение и платформу в один нативный образ для максимально быстрого запуска
Часто задаваемые вопросы
Урок «ReadyToRun и многоуровневая компиляция» бесплатный?
Да — полный текст урока «ReadyToRun и многоуровневая компиляция» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.
Чему я научусь в уроке «ReadyToRun и многоуровневая компиляция»?
Разберитесь в предварительной компиляции ReadyToRun, многоуровневой JIT-компиляции и PGO (оптимизации на основе профиля) в .NET. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать C# Academy?
Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «ReadyToRun и многоуровневая компиляция»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке C# Academy?
Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Компиляция Native AOT
- Обрезка и ограничения отражения
- ReadyToRun и многоуровневая компиляция
- Бенчмаркинг с BenchmarkDotNet