When you compile with NativeAOT (PublishAot=true), your application does not use CoreCLR at all at runtime.
Instead, it uses a minimal, statically linked runtime that’s fundamentally different from CoreCLR, that is a lightweight static runtime called the NativeAOT Runtime (derived from the old CoreRT project)
How it works conceptually
When you publish a .NET app normally:
Standard .NET (JIT/CoreCLR path):
When you publish using NativeAOT, the process changes drastically:
NativeAOT (AOT path):
⚙️ What replaces CoreCLR in NativeAOT?
Instead of the CoreCLR runtime, NativeAOT uses a lightweight static runtime called the NativeAOT Runtime (derived from the old CoreRT project).
This runtime:
- Includes a minimal GC, type system, and exception handling system.
- Does not include JIT compilation logic.
- Does not support full reflection or dynamic assembly loading.
It’s statically linked into the final executable, so the entire runtime becomes part of your .exe file.
🧠Deeper Comparison: CoreCLR vs NativeAOT Runtime
| Feature | CoreCLR | NativeAOT Runtime |
|---|---|---|
| JIT Compiler | ✅ Present (RyuJIT) | ❌ Not included |
| GC (Garbage Collector) | ✅ Full-featured, generational | ✅ Simplified GC (same core concepts, smaller) |
| Type Loader | ✅ Dynamic | ✅ Static (known at build time) |
| Reflection | ✅ Full | ⚠️ Limited (metadata stripped) |
| Dynamic Assembly Load | ✅ Supported | ❌ Not supported |
| Code Generation at Runtime (Emit) | ✅ Supported | ❌ Not supported |
| Startup Time | ⚡ Moderate | ⚡⚡ Super fast |
| Binary Size | 🧱 Larger | 💡 Smaller |
| Runtime Required | .NET runtime (CoreCLR) | None — self-contained |
| Use Cases | General-purpose apps | Cloud, microservices, serverless, CLI tools |
🧮 Example Build Flow
🔸 CoreCLR-based publish:
- app.dll
dotnethost loadsCoreCLRruntime- JIT compiles IL on startup
🔸 NativeAOT publish:
Produces:
-
A single
appexecutable - Contains native code + stripped-down runtime
- Runs directly via
./app(nodotnetneeded)
🧩 Why this matters
- Blazing-fast cold starts (critical for AWS Lambda, Azure Functions, CLI tools)
- No runtime dependency (great for container deployment)
- Reduced attack surface (no JIT, less metadata)
- No runtime code generation (System.Reflection.Emit)
- Limited dynamic loading
- Reflection-heavy frameworks (like some ORMs or JSON serializers) need trimming-safe versions
💡 Internal Implementation Hint
In .NET 7/8, the AOT toolchain (crossgen2) builds an object file (.obj) that includes:
- Compiled IL → native code
- Static GC and metadata tables
- Lightweight runtime startup code (from NativeAOT runtime)
Then it uses the system’s C++ linker to produce a final executable:
So the “runtime” is baked in, not “loaded dynamically” like CoreCLR.
🔧 Analogy
| Mode | Analogy |
|---|---|
| CoreCLR (JIT) | Like a chef who cooks each dish when ordered — flexible, but slower startup. |
| R2R (Partial AOT) | Chef preps ingredients ahead of time — faster serving. |
| NativeAOT (Full AOT) | Meals fully cooked, sealed, and ready to serve — instant serving, no kitchen needed. |
🧾 Summary
| Aspect | CoreCLR (.NET Runtime) | NativeAOT |
|---|---|---|
| Uses JIT? | ✅ Yes | ❌ No |
| Runtime Engine | CoreCLR | NativeAOT runtime (based on CoreRT) |
| Startup Speed | ⚡ Moderate | ⚡⚡ Instant |
| Reflection Support | ✅ Full | ⚠️ Limited |
| Runtime Size | 🧱 Larger | 💡 Small and static |
| Output | IL + runtime | Native binary |
| Target Scenarios | General apps, APIs | Cloud-native, microservices, serverless, tools |
No comments:
Post a Comment