0Pricing
C# Academy · Lekcja

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 startup

Kompilacja 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=0

Optymalizacja 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 throughput

Crossgen2 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 R2R

Praktyczny 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.json i 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

  1. Kompilacja Native AOT
  2. Ograniczenia trimowania i refleksji
  3. ReadyToRun i kompilacja warstwowa
  4. Benchmarki z BenchmarkDotNet
← Powrót do C# Academy