.NET 10 vs .NET 8 vs .NET Framework 4.8.1: Runtime Benchmarks on Windows Server 2025

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

HostWindows Server 2025 Standard, 4 vCPU (AMD EPYC 9004 class), 8 GB RAM, NVMe
Web serverIIS 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 modelASP.NET Core Module v2, in-process hosting for both modern runtimes
App poolSingle pool, Integrated mode, no overlapped recycling during runs
DatabaseSQL Server 2025 on the same host, connection pool warmed before each run
WorkloadJSON API: dependency injection resolution, two EF Core queries, 4 KB serialized payload
Load toolwrk2, 128 connections, 60 s per run, three runs averaged

Throughput and Latency Results

RuntimeRequests/sec (steady)p99 latencyWorking setCold start to first 200
.NET Framework 4.8.111,40058 ms412 MB3.1 s
.NET 8.019,80031 ms298 MB1.4 s
.NET 10.024,60024 ms281 MB1.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.

ConcurrencyFramework 4.8.1.NET 8.0.NET 10.0
649,100 rps16,300 rps19,900 rps
25610,800 rps18,900 rps23,400 rps
5129,600 rps17,100 rps22,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.

Leave a Comment