0Pricing
C# Academy · Lección

ReadyToRun y compilación por niveles

Comprenda la precompilación ReadyToRun, la compilación JIT por niveles y PGO (optimización guiada por perfiles) en .NET.

ReadyToRun y compilación por niveles es una lección gratuita de C# Academy en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de C# Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de C# Academy incluye 4 lecciones en total.

JIT, R2R y compilación por niveles

.NET utiliza varias estrategias de compilación para equilibrar la velocidad de inicio con el rendimiento máximo. Comprender JIT, ReadyToRun y la compilación por niveles le ayuda a elegir la configuración adecuada para su carga de trabajo.

Cómo funciona el JIT

El compilador Just-in-Time (JIT) convierte el código de bytes IL en código máquina nativo la primera vez que se llama a cada método. Esto añade latencia a la primera invocación, pero produce código altamente optimizado para la máquina en ejecución.

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

Compilación ReadyToRun (R2R)

ReadyToRun (R2R) precompila los ensamblados en código nativo durante la publicación. La aplicación se inicia más rápido porque el trabajo del JIT ya está hecho, a cambio de un mayor tamaño en 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 frente a Native AOT

R2R sigue distribuyendo IL junto con el código nativo y aún requiere el runtime de .NET. Native AOT elimina por completo el IL y el runtime. Elija según los requisitos de su implementación.

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

Compilación por niveles

La compilación por niveles está habilitada de forma predeterminada en .NET. Los métodos comienzan en el nivel 0 (compilación rápida) y los métodos activos se vuelven a compilar en el nivel 1 (optimización completa) en segundo plano.

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

Optimización guiada por perfiles (PGO)

.NET 8 y versiones posteriores incluyen PGO dinámica de forma predeterminada. El runtime recopila datos del perfil de llamadas en el nivel 0 y luego utiliza ese perfil para generar código de nivel 1 altamente especializado para las rutas activas.

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

Hooks de inicio

Los hooks de inicio (DOTNET_STARTUP_HOOKS) le permiten inyectar código antes de que se ejecute Main, lo que resulta útil para diagnósticos, sobrescrituras de configuración o instrumentación sin modificar el código de la aplicación.

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

Opciones de configuración del runtime

El runtime de .NET expone variables de entorno y opciones de runtimeconfig.json para ajustar el comportamiento del GC, del grupo de subprocesos y del JIT sin modificar el código.

// 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 imágenes compuestas

Crossgen2 es la herramienta que produce imágenes R2R. La modalidad R2R compuesta precompila una aplicación completa (aplicación y framework) en una sola imagen nativa compartida para lograr el mejor inicio posible.

// 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 real: ajustar el inicio de ASP.NET Core

Una lista de comprobación de configuraciones prácticas para maximizar el rendimiento de inicio de un servicio ASP.NET Core en producción.

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

Comprobación rápida

¿Cuál es la diferencia clave entre ReadyToRun (R2R) y Native AOT?

Recapitulación: ReadyToRun y compilación por niveles

Conclusiones clave:

  • Compilación por niveles: predeterminada en .NET: nivel 0 (compilación rápida) → nivel 1 (optimizado y con PGO)
  • PGO dinámica: predeterminada desde .NET 8; el runtime crea perfiles de las rutas activas y especializa el código de nivel 1
  • ReadyToRun: precompila durante la publicación; inicio aproximadamente un 30 % más rápido; aún necesita el runtime; admite la reflexión
  • Native AOT: sin IL ni runtime; inicio aproximadamente 36 veces más rápido; sin reflexión; el binario más pequeño
  • Ajuste la configuración mediante runtimeconfig.json y variables de entorno (GC, grupo de subprocesos y opciones del JIT)
  • La modalidad R2R compuesta combina la aplicación y el framework en una sola imagen nativa para obtener el mejor inicio

Preguntas frecuentes

¿La lección «ReadyToRun y compilación por niveles» es gratis?

Sí — el texto completo de «ReadyToRun y compilación por niveles» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de C# Academy, actualiza a CoddyKit PRO. El curso de C# Academy incluye 4 lecciones en total.

¿Qué aprenderé en «ReadyToRun y compilación por niveles»?

Comprenda la precompilación ReadyToRun, la compilación JIT por niveles y PGO (optimización guiada por perfiles) en .NET. Practicas C# Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar C# Academy?

No se requiere experiencia previa. C# Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.

¿Cuánto tiempo toma la lección «ReadyToRun y compilación por niveles»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de C# Academy?

Sí. Cada lección de C# Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Compilación Native AOT
  2. Trimming y limitaciones de la reflexión
  3. ReadyToRun y compilación por niveles
  4. Benchmarking con BenchmarkDotNet
← Volver a C# Academy