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 startupCompilació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=0Optimizació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 throughputCrossgen2 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 R2RCaso 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.jsony 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
- Compilación Native AOT
- Trimming y limitaciones de la reflexión
- ReadyToRun y compilación por niveles
- Benchmarking con BenchmarkDotNet