ReadyToRunと階層型コンパイル
.NETにおけるReadyToRunの事前コンパイル、階層型JITコンパイル、PGO(プロファイル誘導最適化)を理解します。
「ReadyToRunと階層型コンパイル」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。
JIT、R2R、階層型コンパイル
.NETは、起動速度とピークスループットのバランスを取るために、複数のコンパイル戦略を使用します。JIT、ReadyToRun、階層型コンパイルを理解すると、ワークロードに適した設定を選択できるようになります。
JITの動作
Just-in-Time(JIT)コンパイラーは、各メソッドが初めて呼び出されたときにILバイトコードをネイティブマシンコードへ変換します。初回呼び出しには遅延が加わりますが、実行中のマシンに合わせて高度に最適化されたコードが生成されます。
// 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)ReadyToRun(R2R)コンパイル
ReadyToRun(R2R)は、発行時にアセンブリをネイティブコードへ事前コンパイルします。JITの処理がすでに完了しているため、アプリはより高速に起動しますが、その代わりディスク上のサイズが大きくなります。
// 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とNative AOTの比較
R2RはネイティブコードとともにILも配布し、.NETランタイムも必要とします。Native AOTはILとランタイムを完全に削除します。デプロイ要件に基づいて選択してください。
// 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階層型コンパイル
.NETでは、階層型コンパイルがデフォルトで有効になっています。メソッドはTier 0(高速コンパイル)から開始し、頻繁に実行されるメソッドはバックグラウンドでTier 1(完全な最適化)に再コンパイルされます。
// 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プロファイルガイド最適化(PGO)
.NET 8以降では、Dynamic PGOがデフォルトで含まれています。ランタイムはTier 0で呼び出しプロファイルデータを収集し、そのプロファイルを使用してホットパス向けに高度に特化されたTier 1コードを生成します。
// 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+)スタートアップフック
スタートアップフック(DOTNET_STARTUP_HOOKS)を使用すると、Mainの実行前にコードを挿入できます。アプリケーションコードを変更せずに、診断、構成の上書き、インストルメンテーションを行う場合に便利です。
// 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]ランタイム構成の設定項目
.NETランタイムは、コードを変更せずにGC、スレッドプール、JITの動作を調整するための環境変数とruntimeconfig.jsonの設定項目を提供します。
// 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と複合イメージ
Crossgen2はR2Rイメージを生成するツールです。複合R2Rはアプリケーション全体(アプリとフレームワーク)を1つの共有ネイティブイメージに事前コンパイルし、可能な限り最高の起動パフォーマンスを実現します。
// 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実践例:ASP.NET Coreの起動を調整する
本番環境のASP.NET Coreサービスで起動パフォーマンスを最大化するための、実践的な設定項目のチェックリストです。
// .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");確認問題
ReadyToRun(R2R)とNative AOTの主な違いは何でしょうか。
まとめ:ReadyToRunと階層型コンパイル
主なポイント:
- 階層型コンパイル:.NETのデフォルト機能。Tier 0(高速コンパイル)からTier 1(最適化とPGO)へ移行します
- Dynamic PGO:.NET 8以降のデフォルト機能。ランタイムがホットパスをプロファイルし、Tier 1コードを特化させます
- ReadyToRun:発行時に事前コンパイルします。起動が約30%高速になり、リフレクションをサポートしますが、ランタイムは必要です
- Native AOT:ILもランタイムもなく、起動は約36倍高速です。リフレクションは使用できず、バイナリが最小になります
runtimeconfig.jsonと環境変数(GC、スレッドプール、JITの設定項目)で調整します- 複合R2Rはアプリとフレームワークを1つのネイティブイメージに統合し、最高の起動パフォーマンスを実現します
よくある質問
「ReadyToRunと階層型コンパイル」レッスンは無料ですか?
はい。「ReadyToRunと階層型コンパイル」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。
「ReadyToRunと階層型コンパイル」で何を学びますか?
.NETにおけるReadyToRunの事前コンパイル、階層型JITコンパイル、PGO(プロファイル誘導最適化)を理解します。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C# Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「ReadyToRunと階層型コンパイル」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC# Academyレッスンでコードを書いて実行できますか?
はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Native AOTコンパイル
- トリミングとリフレクションの制限
- ReadyToRunと階層型コンパイル
- BenchmarkDotNetによるベンチマーク