.NET version upgrades are often treated as compatibility projects: update the target framework, resolve build errors, run tests, and deploy.
For production applications, that approach is incomplete.
A runtime upgrade can also change application performance. Improvements in the JIT compiler, garbage collector, networking stack, libraries, and other runtime components may change CPU consumption, allocations, throughput, and latency.
.NET 10 is an LTS release and is currently in active support, while .NET 8 is in maintenance support and is scheduled to reach end of support on November 10, 2026. .NET 10 is scheduled to remain supported until November 14, 2028.
That makes migration from .NET 8 to .NET 10 both a lifecycle decision and an opportunity to measure whether the newer runtime benefits a specific application.
The important point is this: do not assume that every application will become faster after migration. Benchmark your actual workload.
Why Benchmark .NET 8 Against .NET 10?
Microsoft documents numerous performance improvements in .NET 10 across the runtime and libraries, including JIT optimizations, allocation improvements, LINQ changes, networking improvements, cryptography, and Native AOT.
However, framework-level microbenchmarks are not the same as application-level performance.
An application may be dominated by:
Database latency
Network calls
External APIs
Serialization
File I/O
Business logic
Lock contention
Garbage collection
Thread-pool behavior
A runtime improvement in one subsystem may therefore have little visible effect on an application's end-to-end response time.
The correct comparison is:
Same Application
|
+---- .NET 8
|
+---- .NET 10
|
v
Same Workload
|
v
Compare ResultsEstablish a .NET 8 Baseline
Before migrating, record the application's current behavior.
Useful baseline measurements include:
| Metric | .NET 8 Baseline |
|---|---|
| Requests/second | Measure |
| Average latency | Measure |
| P95 latency | Measure |
| P99 latency | Measure |
| CPU utilization | Measure |
| Memory usage | Measure |
| Allocations | Measure |
| GC activity | Measure |
| Error rate | Measure |
These values must come from your application environment.
Do not substitute synthetic numbers from another application and describe them as migration results.
Keep the Test Environment Consistent
A runtime comparison is only meaningful when the major variables remain controlled.
Try to keep the following consistent:
Application Code
Database
Dataset
Hardware
Operating System
Configuration
Environment Variables
Traffic Pattern
Dependency VersionsThe primary variable should be the runtime:
Test A -> .NET 8
Test B -> .NET 10If you simultaneously change the database, application architecture, hosting platform, and runtime, it becomes difficult to determine what caused the performance difference.
Start With a Simple Benchmark
BenchmarkDotNet is useful for isolated runtime comparisons.
A project can target both frameworks:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet"
Version="0.15.2" />
</ItemGroup>
</Project>The exact package version should be selected according to the current tooling and project requirements.
Microsoft's own .NET performance investigations use BenchmarkDotNet for many runtime comparisons.
Create a CPU-Oriented Benchmark
Consider a simple calculation:
using BenchmarkDotNet.Attributes;
public class ProcessingBenchmark
{
private readonly int[] values =
Enumerable.Range(1, 10_000).ToArray();
[Benchmark]
public long Calculate()
{
long total = 0;
foreach (var value in values)
{
total += value * value;
}
return total;
}
}Run the benchmark against both target frameworks.
The objective is not to prove that .NET 10 is faster in general.
It is to determine whether this particular operation changes under the newer runtime.
Measure Allocations
Runtime performance is not only about execution time.
Memory allocation can affect garbage collection and application throughput.
BenchmarkDotNet can report allocations:
[MemoryDiagnoser]
public class AllocationBenchmark
{
[Benchmark]
public string BuildMessage()
{
return string.Concat(
"Order: ",
1001,
" Status: ",
"Active");
}
}The comparison should capture:
Execution Time
Allocated Bytes
GC CollectionsIf .NET 10 reduces allocations for a frequently executed operation, the application may benefit even when the individual operation's execution time changes only slightly.
Test JSON Serialization
Modern APIs often spend significant CPU time serializing and deserializing data.
A simple benchmark can compare a representative object:
using System.Text.Json;
public class SerializationBenchmark
{
private readonly Order order = new()
{
Id = 1001,
CustomerId = 2001,
Status = "Active",
Total = 249.50m
};
[Benchmark]
public string Serialize()
{
return JsonSerializer.Serialize(order);
}
}For a meaningful test, use representative payload sizes.
A tiny object may not reveal behavior that becomes important when an API processes large responses.
Test LINQ Workloads
LINQ is another useful area because runtime and library optimizations can influence common operations.
For example:
[Benchmark]
public int CalculateActiveOrders()
{
return orders
.Where(x => x.Status == "Active")
.Select(x => x.Quantity)
.Sum();
}Test different dataset sizes:
1,000 records
10,000 records
100,000 records
1,000,000 recordsThe actual sizes should reflect the application's workload.
Microsoft's .NET 10 performance work includes improvements affecting LINQ and other library operations, but the impact of those changes depends on how an application uses the APIs.
Test HTTP Workloads
For web applications, HTTP performance matters more than isolated arithmetic.
A useful benchmark can measure calls through the application's actual API.
For example:
Client
|
v
ASP.NET Core
|
+-- Authentication
+-- Business Logic
+-- Database
|
v
ResponseCapture:
Requests per second
P50 latency
P95 latency
P99 latency
Error rate
CPU
Memory
Do not benchmark only a controller method if the production workload spends most of its time waiting for a database.
Database-Heavy Applications Need Special Treatment
Suppose an API spends:
5 ms Application Processing
80 ms Database
10 ms NetworkA 20% improvement in application processing would reduce approximately 1 ms from the overall request path.
That may be useful, but it will not transform the 95 ms request into a 50 ms request.
This is why migration benchmarking should identify where the application's time is actually spent.
Request
|
+-- Application CPU
+-- Database
+-- Network
+-- Serialization
+-- External ServicesBenchmark the dominant components.
Measure Garbage Collection
Applications with significant allocations should examine GC behavior.
Useful indicators include:
Gen 0 collections
Gen 1 collections
Gen 2 collections
Allocated bytes
Pause behavior
Heap size
A benchmark should not conclude that one runtime is better simply because its memory usage appears lower at one point in time.
Observe the application across a sustained workload.
Use Sustained Load Testing
A short benchmark can miss behavior that appears after several minutes or hours.
A better application-level test is:
Warm-up
|
v
Normal Load
|
v
Sustained Load
|
v
Higher Load
|
v
RecoveryDuring the test, collect:

Join the conversation! Your thoughts help the community grow.