0Pricing
C# Academy · Lezione

ReadyToRun e compilazione a livelli

Comprenda la precompilazione ReadyToRun, la compilazione JIT a livelli e la PGO (ottimizzazione guidata dai profili) in .NET.

ReadyToRun e compilazione a livelli è una lezione C# Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento C# Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso C# Academy include 4 lezioni in totale.

JIT, R2R e compilazione a livelli

.NET usa diverse strategie di compilazione per bilanciare la velocità di avvio e il throughput massimo. Comprendere JIT, ReadyToRun e la compilazione a livelli aiuta a scegliere le impostazioni corrette per il proprio carico di lavoro.

Come funziona il JIT

Il compilatore Just-in-Time (JIT) converte il bytecode IL in codice macchina nativo la prima volta che viene chiamato ogni metodo. Questo aggiunge latenza alla prima invocazione, ma produce codice altamente ottimizzato per la macchina in esecuzione.

// 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)

Compilazione ReadyToRun (R2R)

ReadyToRun (R2R) precompila gli assembly in codice nativo durante la pubblicazione. L'app si avvia più rapidamente perché il lavoro del JIT è già stato eseguito, al prezzo di una maggiore dimensione su disco.

// 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 e Native AOT a confronto

R2R distribuisce ancora l'IL insieme al codice nativo e richiede comunque il runtime .NET. Native AOT rimuove completamente sia l'IL sia il runtime. Scelga in base ai requisiti di distribuzione.

// 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

Compilazione a livelli

La compilazione a livelli è abilitata per impostazione predefinita in .NET. I metodi iniziano al livello 0 (compilazione rapida) e quelli più utilizzati vengono ricompilati in background al livello 1 (ottimizzazione completa).

// 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

Ottimizzazione guidata dai profili (PGO)

.NET 8 e versioni successive includono Dynamic PGO per impostazione predefinita. Il runtime raccoglie i dati del profilo delle chiamate al livello 0, quindi usa tale profilo per generare codice di livello 1 altamente specializzato per i percorsi più utilizzati.

// 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+)

Hook di avvio

Gli hook di avvio (DOTNET_STARTUP_HOOKS) consentono di inserire codice prima dell'esecuzione di Main. Sono utili per la diagnostica, le sostituzioni della configurazione o la strumentazione senza modificare il codice dell'applicazione.

// 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]

Parametri di configurazione del runtime

Il runtime .NET espone variabili d'ambiente e parametri di runtimeconfig.json per ottimizzare il comportamento del GC, del pool di thread e del JIT senza modificare il codice.

// 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 e immagini composite

Crossgen2 è lo strumento che produce le immagini R2R. R2R Composite precompila un'intera applicazione (applicazione e framework) in un'unica immagine nativa condivisa, per ottenere il miglior avvio possibile.

// 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

Caso reale: ottimizzazione dell'avvio di ASP.NET Core

Un elenco pratico delle impostazioni per massimizzare le prestazioni di avvio di un servizio ASP.NET Core in produzione.

// .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");

Verifica rapida

Qual è la differenza fondamentale tra ReadyToRun (R2R) e Native AOT?

Riepilogo: ReadyToRun e compilazione a livelli

Punti chiave:

  • Compilazione a livelli: predefinita in .NET — livello 0 (compilazione rapida) → livello 1 (ottimizzato e con PGO)
  • Dynamic PGO: predefinito da .NET 8 in poi — il runtime crea il profilo dei percorsi più utilizzati e specializza il codice di livello 1
  • ReadyToRun: precompila durante la pubblicazione; avvio più rapido di circa il 30%; richiede comunque il runtime; supporta la reflection
  • Native AOT: niente IL e niente runtime; avvio circa 36 volte più rapido; nessuna reflection; binario più piccolo
  • Ottimizzi tramite runtimeconfig.json e le variabili d'ambiente (GC, pool di thread e parametri del JIT)
  • R2R Composite unisce applicazione e framework in un'unica immagine nativa per ottenere il miglior avvio

Domande Frequenti

La lezione «ReadyToRun e compilazione a livelli» è gratuita?

Sì — il testo completo di «ReadyToRun e compilazione a livelli» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso C# Academy, passa a CoddyKit PRO. Il corso C# Academy include 4 lezioni in totale.

Cosa imparerò in «ReadyToRun e compilazione a livelli»?

Comprenda la precompilazione ReadyToRun, la compilazione JIT a livelli e la PGO (ottimizzazione guidata dai profili) in .NET. Eserciti C# Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare C# Academy?

Non è richiesta alcuna esperienza precedente. C# Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «ReadyToRun e compilazione a livelli»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione C# Academy?

Sì. Ogni lezione C# Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Compilazione Native AOT
  2. Trimming e limitazioni della reflection
  3. ReadyToRun e compilazione a livelli
  4. Benchmark con BenchmarkDotNet
← Torna a C# Academy