Data Layer at Scale — series:
- EF Core 10 AOT: what’s broken (link TBD)
- Dapper.AOT broken on .NET 10 (link TBD)
- Extern alias benchmarking (link TBD)
- Cold start vs density (this article)
- Allocation numbers for k8s (link TBD)
- The benchmark lied (link TBD)
- The data layer as a line item (link TBD)
- Measure, then migrate (link TBD)
Many entries demonstrating significant speed benefits and binary-size advantages may be found by searching for NativeAOT benchmarks. An honest examination of cold-start latency, which is the measure most developers consider when asking “should I use NativeAOT?” is something you won’t see as frequently.
While investigating the data layer for an enterprise.NET framework aimed at Kubernetes-dense deployments, I conducted these observations. Twice, the outcomes did not match my expectations.
The Assumption
The conventional wisdom: JIT compilation happens at runtime, so there’s inherent warmup latency. NativeAOT compiles everything ahead of time. Therefore NativeAOT cold start should be dramatically faster.
Sounds airtight. It’s mostly wrong for typical ASP.NET Core services.
The Setup
- .NET 10 (net10.0), x64, local 4 vCPU / 8 GB Windows workstation — read the numbers as relative comparisons, not absolute SLAs
- ASP.NET Core minimal API service — a small CRUD app (one Order domain, ~5 handlers: create, read-by-id, list, delete, plus a transactional outbox), with DI, routing, JSON and DB init. Deliberately lean, to isolate the provider — so the JIT-able startup surface is intentionally small (this matters for the cold-start reading below).
- SQLite via three providers: raw ADO.NET, Dapper, EF Core
- Cold start = process launch → first HTTP 200, tight polling loop from a sidecar; medians of repeated runs
- Publish: self-contained for JIT,
PublishAotfor native
The Numbers
| Configuration | Binary Size | Cold Start | Idle RSS | RAM-fit replicas* |
|---|---|---|---|---|
| ADO.NET — JIT self-contained | 108 MB | ~601 ms | 49 MB | 155 |
| ADO.NET — AOT native | 13 MB | ~530 ms | 23 MB | 335 |
| Dapper — JIT self-contained | 108 MB | ~590 ms | 51 MB | 150 |
| EF Core — JIT self-contained | 115 MB | ~1,279 ms | 77 MB | 99 |
floor((8192 − 512) / RSS) on a 4 vCPU / 8 GB node, computed from unrounded measured RSS. This is the RAM ceiling, not a serving-capacity claim — nobody runs 335 pods on 4 vCPUs; the scheduler’s per-pod CPU requests bind the count long before RAM does (at a typical 50-millicore request, 80 pods — for every provider alike). What the column actually tells you is whether RAM or CPU is your binding constraint: at 77 MB it’s RAM for any request under ~40m; at 23 MB, RAM effectively leaves the constraint set.
Surprise #1: The Cold-Start Gap Is ~70 ms
The AOT binary shaves roughly 70 ms off cold start versus its JIT twin. For most deployments that’s noise. Why so small?
For a small-to-medium service, cold start is not dominated by JIT compilation. It’s dominated by work that exists identically in both modes:
- DI container construction — registrations, graph validation, root
ServiceProvider - Middleware pipeline assembly — routing tables, endpoint metadata
- Database initialization — connection, journal mode, schema checks
- Hosted services and configuration binding
AOT eliminates runtime bring-up almost entirely — but that was the small slice.
The real cold-start villain is EF Core’s startup overhead — model-building chiefly: walking entity types, building the internal model, compiling query pipelines adds ~680 ms on top of everything, JIT or not. If cold start matters to you, your ORM choice moves the needle ~10× more than your compilation mode. (EF’s compiled-model feature targets exactly this — a separate optimization with its own constraints.)
Does this scale to a bigger app? These numbers are from a deliberately small service, so read the ~70 ms as what we measured here, not a universal law. A larger app JIT-compiles more startup code and loads more assemblies, so AOT’s absolute cold-start saving would likely grow. Two things temper that, though: the init costs above (DI, DB, config, EF model) scale right along with the app and AOT does not remove them, and ReadyToRun (PublishReadyToRun=true) recovers most of the startup-JIT time without AOT’s trimming constraints. So expect “somewhat more,” not automatically “dramatically faster.” If cold start is your deciding factor — or your app is reflection-heavy, serialization-heavy, or has a large dependency graph — measure it on your own service (and try R2R before reaching for AOT).
Surprise #2: Where AOT Actually Pays
Look at the last two columns again — then look at what happened under sustained load (8 workers × 60 s, the app’s own runtime counters):
| ADO.NET | CPU cores used | req/s | RSS peak | Gen0 GCs | GC pause total |
|---|---|---|---|---|---|
| JIT | 0.67 | 2,368 | 72 MB | 375 | 364 ms |
| AOT | 0.50 | 2,580 | 37 MB | 193 | 231 ms |
Same source code. Under load, AOT cut CPU by 25%, RSS by 49%, and Gen0 collections by 49% — while serving slightly more requests. The micro-benchmark view (“AOT is a size/memory play”) undersells it: process-level JIT activity, code-heap memory, and the GC pressure they feed are real CPU costs that only show up under sustained traffic.
So the honest mental model:
- Not a cold-start optimization (~70 ms).
- A per-core-throughput and density optimization: −25% CPU under load, 8× smaller image (pull times, layer cache, rollout I/O), and an RSS low enough that RAM stops being a bin-packing constraint at all (see the replica-fit note under the table).
A Practical Decision Guide
Choose NativeAOT when:
- Pod density or total cluster cost is the constraint
- Sustained CPU per request matters (it’s your infra bill)
- Your dependency stack is AOT-clean — raw ADO.NET is; EF Core and Dapper.AOT currently are not (see the companion articles for the reproducible proof)
Stick with JIT self-contained when:
- Cold start is your only motivation — 70 ms rarely justifies the toolchain cost
- You depend on EF Core’s productivity and can spend RAM instead of engineering time
- Reflection-heavy libraries in your graph fight the trimmer
For engineering leaders: AOT’s ROI shows up on the node bill, not the latency dashboard. Cutting CPU 25% cuts the core count you pay for, and halving RSS means RAM never caps your bin-packing before your CPU budget does. But the prerequisite is a data layer that’s AOT-clean — which today is an architectural decision, not a compiler flag.
Conclusion
NativeAOT is a genuine win — just not where most people look for it. Cold start improves by ~70 ms on a lean stack and not at all where EF model-building dominates. The wins that survive contact with production are binary size, idle RSS, and CPU under load — a cost story, and a strong one.
Benchmark your actual service, under actual load, with in-app counters — not just a micro-benchmark. Ours changed our conclusions twice.
Measured on .NET 10 (net10.0), Microsoft.Data.Sqlite / EFCore.Sqlite 10.0.9, Dapper 2.1.66, single x64 host; medians of repeated runs; load metrics self-reported via Environment.CpuUsage and GC counters.
Part of the Data Layer at Scale series — research notes from building Turboservices, a .NET framework for rebuilding legacy systems onto AI-native delivery rails.
About the author — Vitalii Honcharuk is a hands-on Distinguished Engineer, Architect and CTO with 15+ years of experience building frameworks and high-reliability backend systems for enterprises and startups. His current work, Turboservices, turns engineering discipline into compile-time guarantees: unsafe states refuse to build, quality gates are code, and every architectural decision ships with measured evidence.
Recommendation for ASP.NET 10.0 Hosting
A solid base for developing online services and applications is ASP.NET. Before creating an ASP.NET web application, you must be proficient in JavaScript, HTML, CSS, and C#. There are thousands of web hosting providers offering ASP.NET hosting on the market. However, there are relatively few web hosting providers that offer top-notch ASP.NET hosting.
ASP.NET is the best development language in Windows platform, which is released by Microsoft and widely used to build all types of dynamic Web sites and XML Web services. With this article, we’re going to help you to find the best ASP.NET Hosting solution in Europe based on reliability, features, price, performance and technical support. After we reviewed about 30+ ASP.NET hosting providers in Europe, our Best ASP.NET Hosting Award in Europe goes to HostForLIFE.eu, one of the fastest growing private companies and one of the most reliable hosting providers in Europe.
