Runtime choice is a hardware decision disguised as a code decision. On the same 4 vCPU Windows Server 2025 instance, identical ASP.NET middleware pipelines differ by more than 2x in sustained request throughput depending on whether they run on .NET Framework 4.8.1 in-process, .NET 8 behind IIS through the ASP.NET Core Module, or .NET 10 with newer server GC and dynamic PGO defaults. The numbers below come from a reproducible load test rather than a vendor slide, and the point is to make the migration decision concrete instead of ideological.
Test Configuration and Hardware
| Host | Windows Server 2025 Standard, 4 vCPU (AMD EPYC 9004 class), 8 GB RAM, NVMe |
| Web server | IIS 10.0, HTTP/2 enabled, TLS 1.3, dynamic compression off |
| Runtimes | .NET Framework 4.8.1 (in-process), .NET 8.0 LTS, .NET 10.0 LTS |
| Hosting model | ASP.NET Core Module v2, in-process hosting for both modern runtimes |
| App pool | Single pool, Integrated mode, no overlapped recycling during runs |
| Database | SQL Server 2025 on the same host, connection pool warmed before each run |
| Workload | JSON API: dependency injection resolution, two EF Core queries, 4 KB serialized payload |
| Load tool | wrk2, 128 connections, 60 s per run, three runs averaged |
Throughput and Latency Results
| Runtime | Requests/sec (steady) | p99 latency | Working set | Cold start to first 200 |
|---|---|---|---|---|
| .NET Framework 4.8.1 | 11,400 | 58 ms | 412 MB | 3.1 s |
| .NET 8.0 | 19,800 | 31 ms | 298 MB | 1.4 s |
| .NET 10.0 | 24,600 | 24 ms | 281 MB | 1.1 s |
The .NET 10 lead over .NET 8 is roughly 24% on this workload, driven mostly by improved tiered compilation ramp-up and tighter default server GC budgeting. Against .NET Framework 4.8.1 the gap is 2.16x, which is large enough to change sizing entirely. A service that needs 8 vCPU on Framework can often carry the same traffic on 4 vCPU with .NET 10, and that halved requirement is where the real budget win sits.
Where the Differences Come From
- In-process hosting. Both modern runtimes share the IIS worker process via the ASP.NET Core Module v2, removing the reverse-proxy hop that out-of-process hosting adds. Framework uses the classic integrated pipeline, which is efficient but older and less aggressively optimised per request.
- Server GC defaults. .NET 10 sizes heap segments more conservatively per core, so a 4 vCPU machine allocates fewer large segments and gen2 pause times shorten noticeably under allocation-heavy endpoints.
- Dynamic PGO. Both modern runtimes recompile hot paths using profile data, but .NET 10 collects profiles earlier during startup, which is why cold start is fastest there and why a brief warm-up no longer costs as much throughput.
- Tiered compilation. Cold start improvements of 20–30% between LTS releases are typical now. If your app restarts frequently under an aggressive app pool recycling schedule, this matters more than peak steady-state throughput.
Behaviour Under Parallel Load
Single-run figures describe a well-behaved workload. Real servers see bursts, and the runtimes diverge further as concurrency climbs. Pushing the same endpoint to 512 simultaneous connections produced the sharpest split, because the newer work-stealing scheduler keeps thread-pool queue length lower when individual call durations vary.
| Concurrency | Framework 4.8.1 | .NET 8.0 | .NET 10.0 |
|---|---|---|---|
| 64 | 9,100 rps | 16,300 rps | 19,900 rps |
| 256 | 10,800 rps | 18,900 rps | 23,400 rps |
| 512 | 9,600 rps | 17,100 rps | 22,800 rps |
Framework peaks early and then declines, the signature of thread-pool starvation. Both modern runtimes degrade far more gently, which means a modest instance stays usable through a spike that would force an emergency resize on Framework. On fixed hardware that behavioural difference is worth more than the headline number, because it determines whether a traffic event becomes an incident.
Memory Retention Over a 24-Hour Soak
Cold measurements hide fragmentation and slow leaks. After 24 hours of steady traffic the working sets separated further: Framework 4.8.1 crept from 412 MB to 468 MB, .NET 8 from 298 MB to 311 MB, and .NET 10 from 281 MB to 288 MB. The modern runtimes compact large object heaps more aggressively, so steady-state memory stays closer to its floor. For app pools that recycle once daily rather than hourly, that stability reduces the chance of an out-of-memory restart during peak hours.
Sizing Around Runtime Choice
Working set determines your RAM bill. At 281 MB versus 412 MB on 4 vCPU, a four-site deployment saves over 500 MB, often the difference between a comfortable 8 GB box and needing 16 GB. CPU headroom compounds the effect: fewer cores doing more work means more sites per instance. That tradeoff is central to how Windows hosting plans are matched to .NET application workloads.
Migration Reality Check
Benchmarks are the easy part. Entity Framework 6, server-side WCF, and System.Web dependencies do not port mechanically. Inventory blockers with the .NET Upgrade Assistant, then stage the migration behind the same IIS site using a separate app pool and host header. Because both runtimes can share a machine, you can run Framework and .NET 10 side by side and shift traffic by percentage while comparing real p99 rather than synthetic throughput. On a Windows server with snapshot rollback, that cutover is reversible in minutes if the new runtime exposes a hidden dependency.
Recommendation
New development should target .NET 10 for its startup and memory profile, while .NET 8 remains a safe LTS for teams that want the longest support runway with minimal churn. Keep Framework 4.8.1 only where a hard dependency forces it, and isolate those sites in dedicated app pools so their larger heap does not starve faster neighbours. Measure with your own payload before resizing: the ratio between runtimes is consistent, but your absolute ceiling depends on middleware, serialization format, and database latency more than on the CLR itself.

