ReadyToRun i kompilacja warstwowa
Poznaj wstępną kompilację ReadyToRun, warstwową kompilację JIT oraz PGO (optymalizację sterowaną profilem) w .NET.
ReadyToRun i kompilacja warstwowa to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.
JIT, R2R i kompilacja warstwowa
.NET używa kilku strategii kompilacji, aby zachować równowagę między szybkością uruchamiania a maksymalną przepustowością. Zrozumienie JIT, ReadyToRun i kompilacji warstwowej pomaga wybrać odpowiednie ustawienia dla danego obciążenia.
Jak działa JIT
Kompilator Just-in-Time (JIT) konwertuje kod bajtowy IL na natywny kod maszynowy przy pierwszym wywołaniu każdej metody. Zwiększa to opóźnienie pierwszego wywołania, ale pozwala wygenerować wysoce zoptymalizowany kod dla uruchomionej maszyny.
// 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)Kompilacja ReadyToRun (R2R)
ReadyToRun (R2R) wstępnie kompiluje zestawy do kodu natywnego podczas publikowania. Aplikacja uruchamia się szybciej, ponieważ praca JIT została już wykonana, kosztem większego rozmiaru na dysku.
// 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 a Native AOT
R2R nadal dostarcza kod IL wraz z kodem natywnym i wciąż wymaga środowiska uruchomieniowego .NET. Native AOT całkowicie usuwa kod IL i środowisko uruchomieniowe. Wyboru należy dokonać na podstawie wymagań dotyczących wdrożenia.
// 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 startupKompilacja warstwowa
Kompilacja warstwowa jest domyślnie włączona w .NET. Metody rozpoczynają działanie na poziomie Tier 0 (szybka kompilacja), a często używane metody są w tle ponownie kompilowane na poziomie Tier 1 (pełna optymalizacja).
// 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=0Optymalizacja sterowana profilem (PGO)
Wersja .NET 8 i nowsze domyślnie obejmuje dynamiczne PGO. Środowisko uruchomieniowe zbiera dane o profilu wywołań na poziomie Tier 0, a następnie używa tego profilu do generowania wysoce wyspecjalizowanego kodu Tier 1 dla często wykonywanych ścieżek.
// 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+)Haki uruchomieniowe
Haki uruchomieniowe (DOTNET_STARTUP_HOOKS) umożliwiają wstrzyknięcie kodu przed uruchomieniem Main. Jest to przydatne w diagnostyce, zastępowaniu konfiguracji lub instrumentacji bez modyfikowania kodu aplikacji.
// 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]Ustawienia konfiguracji środowiska uruchomieniowego
Środowisko uruchomieniowe .NET udostępnia zmienne środowiskowe i ustawienia w pliku runtimeconfig.json, które pozwalają dostosować działanie GC, puli wątków i JIT bez zmian w kodzie.
// 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 i obrazy kompozytowe
Crossgen2 to narzędzie tworzące obrazy R2R. Kompozytowe R2R wstępnie kompiluje całą aplikację (aplikację i platformę) do jednego współdzielonego obrazu natywnego, zapewniając możliwie najszybsze uruchamianie.
// 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 R2RPraktyczny przykład: dostrajanie uruchamiania ASP.NET Core
Lista praktycznych ustawień pozwalających zmaksymalizować wydajność uruchamiania usługi ASP.NET Core w środowisku produkcyjnym.
// .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");Szybkie sprawdzenie
Jaka jest najważniejsza różnica między ReadyToRun (R2R) a Native AOT?
Podsumowanie: ReadyToRun i kompilacja warstwowa
Najważniejsze informacje:
- Kompilacja warstwowa: domyślna w .NET — Tier 0 (szybka kompilacja) → Tier 1 (optymalizacja i PGO)
- Dynamiczne PGO: domyślne w .NET 8 i nowszych — środowisko uruchomieniowe profiluje często wykonywane ścieżki i dostosowuje do nich kod Tier 1
- ReadyToRun: wstępna kompilacja podczas publikowania; około 30% szybsze uruchamianie; nadal wymaga środowiska uruchomieniowego; obsługuje refleksję
- Native AOT: brak kodu IL i środowiska uruchomieniowego; około 36 razy szybsze uruchamianie; brak refleksji; najmniejszy plik binarny
- Dostrajanie odbywa się za pomocą
runtimeconfig.jsoni zmiennych środowiskowych (ustawienia GC, puli wątków i JIT) - Kompozytowe R2R łączy aplikację i platformę w jeden obraz natywny, zapewniając najszybsze uruchamianie
Często zadawane pytania
Czy lekcja „ReadyToRun i kompilacja warstwowa” jest bezpłatna?
Tak — pełny tekst „ReadyToRun i kompilacja warstwowa” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „ReadyToRun i kompilacja warstwowa”?
Poznaj wstępną kompilację ReadyToRun, warstwową kompilację JIT oraz PGO (optymalizację sterowaną profilem) w .NET. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć C# Academy?
Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „ReadyToRun i kompilacja warstwowa”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?
Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Kompilacja Native AOT
- Ograniczenia trimowania i refleksji
- ReadyToRun i kompilacja warstwowa
- Benchmarki z BenchmarkDotNet