ReadyToRun และการคอมไพล์แบบแบ่งระดับ
ทำความเข้าใจการคอมไพล์ล่วงหน้าด้วย ReadyToRun การคอมไพล์ JIT แบบแบ่งระดับ และ PGO (การปรับให้เหมาะสมตามโพรไฟล์) ใน .NET
ReadyToRun และการคอมไพล์แบบแบ่งระดับ เป็นบทเรียน C# Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C# Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน
JIT, R2R และการคอมไพล์แบบแบ่งระดับ
.NET ใช้กลยุทธ์การคอมไพล์หลายแบบเพื่อสร้างสมดุลระหว่างความเร็วในการเริ่มต้นกับประสิทธิภาพสูงสุด การทำความเข้าใจ JIT, ReadyToRun และการคอมไพล์แบบแบ่งระดับจะช่วยให้คุณเลือกการตั้งค่าที่เหมาะกับงานของคุณ
การทำงานของ JIT
คอมไพเลอร์แบบคอมไพล์ทันเวลา (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+ มี 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 เปิดให้ใช้ตัวแปรสภาพแวดล้อมและตัวควบคุมใน runtimeconfig.json เพื่อปรับแต่งพฤติกรรมของ GC พูลเธรด และ JIT โดยไม่ต้องแก้ไขโค้ด
// 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 ส่วน Composite R2R จะคอมไพล์ทั้งแอปพลิเคชัน (แอปและเฟรมเวิร์ก) ล่วงหน้าเป็นอิมเมจเนทีฟที่ใช้ร่วมกันเพียงหนึ่งเดียว เพื่อให้เริ่มต้นได้ดีที่สุด
// 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)
- PGO แบบไดนามิก: เป็นค่าเริ่มต้นใน .NET 8+ — รันไทม์สร้างโปรไฟล์เส้นทางที่ถูกเรียกใช้บ่อยและปรับโค้ด Tier 1 ให้เหมาะเฉพาะทาง
- ReadyToRun: คอมไพล์ล่วงหน้าขณะเผยแพร่ เริ่มต้นเร็วขึ้นประมาณ 30% ยังคงต้องใช้รันไทม์ และรองรับรีเฟล็กชัน
- Native AOT: ไม่มี IL และไม่มีรันไทม์ เริ่มต้นเร็วขึ้นประมาณ 36 เท่า ไม่รองรับรีเฟล็กชัน และมีไบนารีขนาดเล็กที่สุด
- ปรับแต่งผ่าน
runtimeconfig.jsonและตัวแปรสภาพแวดล้อม (ตัวควบคุม GC พูลเธรด และ JIT) - Composite R2R รวมแอปและเฟรมเวิร์กไว้ในอิมเมจเนทีฟเดียวเพื่อให้เริ่มต้นได้ดีที่สุด
คำถามที่พบบ่อย
บทเรียน “ReadyToRun และการคอมไพล์แบบแบ่งระดับ” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ReadyToRun และการคอมไพล์แบบแบ่งระดับ” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C# Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ReadyToRun และการคอมไพล์แบบแบ่งระดับ”
ทำความเข้าใจการคอมไพล์ล่วงหน้าด้วย ReadyToRun การคอมไพล์ JIT แบบแบ่งระดับ และ PGO (การปรับให้เหมาะสมตามโพรไฟล์) ใน .NET คุณปฏิบัติ C# Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C# Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน C# Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “ReadyToRun และการคอมไพล์แบบแบ่งระดับ” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน C# Academy นี้ได้ไหม
ได้ บทเรียน C# Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การคอมไพล์ Native AOT
- ข้อจำกัดของการตัดโค้ดและ Reflection
- ReadyToRun และการคอมไพล์แบบแบ่งระดับ
- การวัดประสิทธิภาพด้วย BenchmarkDotNet