0Pricing
C# Academy · บทเรียน

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 throughput

Crossgen2 และอิมเมจแบบคอมโพสิต

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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การคอมไพล์ Native AOT
  2. ข้อจำกัดของการตัดโค้ดและ Reflection
  3. ReadyToRun และการคอมไพล์แบบแบ่งระดับ
  4. การวัดประสิทธิภาพด้วย BenchmarkDotNet
← กลับไปที่ C# Academy