.NET 8 makes you faster. Everyday.
As has been the case for many releases, the .NET 8 team is spending a lot of development effort to improve the .NET Runtime in terms of performance. Because fast performance means one thing above all: low energy consumption and high scalability.
Thus, .NET 8 has again received some performance improvements, which have a positive effect in many areas. A summary and partly also deep-dive explanations have been provided by Stephen Toub in the .NET Blog: Performance Improvements in .NET 8
I took the release of .NET 8 RC2 as an opportunity to revise my compilation, removing samples with .NET 6 and implementing new samples with .NET 7 and .NET 8.
You can find them on my GitHub at 🌳 Sustainable Code - by BEN ABT
Performance improvements
Here's a quick overview of which of my samples have improved tremendously in terms of performance.
🌳 Condition vs Exception
This sample shows the implementation of Application Flows vs Exceptions. We see a huge performance boost in .NET 8.
BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 10 (10.0.19045.3570/22H2/2022Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 8.0.100-rc.2.23502.2
[Host] : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
| Method | Job | Runtime | Mean | Error | StdDev | Gen0 | Allocated | Alloc Ratio |
|---------- |--------- |--------- |-------------:|-----------:|-----------:|-------:|----------:|------------:|
| Condition | .NET 7.0 | .NET 7.0 | 19.482 ns | 0.3892 ns | 0.3641 ns | - | - | NA |
| Condition | .NET 8.0 | .NET 8.0 | 1.612 ns | 0.0315 ns | 0.0295 ns | - | - | NA |
| | | | | | | | | |
| Exception | .NET 7.0 | .NET 7.0 | 3,547.191 ns | 48.9747 ns | 43.4148 ns | 0.0114 | 232 B | NA |
| Exception | .NET 8.0 | .NET 8.0 | 2,003.474 ns | 44.8129 ns | 41.9180 ns | 0.0076 | 232 B | NA |
🌳 Sustainable Code - Condition vs Exception
🌳 Sustainable Code - Create Empty Collections
Creating empty lists is now twice as fast with .NET 8!
BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 10 (10.0.19045.3570/22H2/2022Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 8.0.100-rc.2.23502.2
[Host] : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
| Method | Job | Runtime | Mean | Error | StdDev | Gen0 | Allocated |
|---------------- |--------- |--------- |----------:|----------:|----------:|-------:|----------:|
| List | .NET 7.0 | .NET 7.0 | 15.012 ns | 0.3203 ns | 0.7486 ns | 0.0043 | 72 B |
| List | .NET 8.0 | .NET 8.0 | 8.932 ns | 0.1964 ns | 0.1741 ns | 0.0019 | 32 B |
🌳 Sustainable Code - Create Empty Collections
DateTimeOffset vs. DateTime
The handling of the DateTime and DateTimeOffset structs has increased by 30%.
BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 10 (10.0.19045.3570/22H2/2022Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 8.0.100-rc.2.23502.2
[Host] : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
| Method | Runtime | Mean | Error | StdDev | Ratio | RatioSD |
|---------------------- |--------- |----------:|---------:|---------:|------:|--------:|
| DateTimeOffset_UtcNow | .NET 7.0 | 29.25 ns | 0.256 ns | 0.227 ns | 1.00 | 0.00 |
| DateTimeOffset_Now | .NET 7.0 | 97.67 ns | 1.710 ns | 1.600 ns | 3.35 | 0.07 |
| DateTime_UtcNow | .NET 7.0 | 30.24 ns | 0.277 ns | 0.231 ns | 1.03 | 0.01 |
| DateTime_Now | .NET 7.0 | 148.96 ns | 2.050 ns | 1.917 ns | 5.09 | 0.04 |
| | | | | | | |
| DateTimeOffset_UtcNow | .NET 8.0 | 23.90 ns | 0.025 ns | 0.019 ns | 1.00 | 0.00 |
| DateTimeOffset_Now | .NET 8.0 | 72.49 ns | 1.222 ns | 1.143 ns | 3.04 | 0.05 |
| DateTime_UtcNow | .NET 8.0 | 27.11 ns | 0.521 ns | 0.487 ns | 1.13 | 0.02 |
| DateTime_Now | .NET 8.0 | 114.42 ns | 1.317 ns | 1.232 ns | 4.80 | 0.05 |
🌳 Sustainable Code - DateTimeOffset vs. DateTime
less allocations (and better performance) with NetFabric.Hyperlinq
We see a significant performance boost of over 50% in some cases across many Linq implementations! This is a huge improvement considering that almost every data operation of every .NET application is implemented via Linq these days.
BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 10 (10.0.19045.3570/22H2/2022Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 8.0.100-rc.2.23502.2
[Host] : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
| Method | Runtime | Mean | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|---------------- |--------- |---------:|------:|--------:|-------:|-------:|----------:|------------:|
| SystemLinqWhere | .NET 7.0 | 3.237 us | 1.00 | 0.00 | 0.5074 | 0.0038 | 8.3 KB | 1.00 |
| SystemLinqWhere | .NET 8.0 | 2.106 us | 1.00 | 0.00 | 0.5074 | 0.0038 | 8.3 KB | 1.00 |
| | | | | | | | | |
| HyperLinqWhere | .NET 7.0 | 2.146 us | 0.66 | 0.05 | 0.2174 | - | 3.57 KB | 0.43 |
| HyperLinqWhere | .NET 8.0 | 1.471 us | 0.69 | 0.02 | 0.2174 | 0.0019 | 3.57 KB | 0.43 |
🌳 Sustainable Code - less allocations (and better performance) with NetFabric.Hyperlinq 📊
In Param
In-parameters in .NET 8 are now twice as fast as in .NET 7.
BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 10 (10.0.19045.3570/22H2/2022Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 8.0.100-rc.2.23502.2
[Host] : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
| Method | Runtime | Mean | Error | StdDev | Ratio |
|---------------- |--------- |----------:|----------:|----------:|------:|
| With_InParam | .NET 7.0 | 0.4264 ns | 0.0247 ns | 0.0219 ns | 2.10 |
| With_InParam | .NET 8.0 | 0.2000 ns | 0.0084 ns | 0.0070 ns | 1.10 |
| | | | | | |
| WithOut_InParam | .NET 7.0 | 0.2051 ns | 0.0163 ns | 0.0153 ns | 1.00 |
| WithOut_InParam | .NET 8.0 | 0.1842 ns | 0.0092 ns | 0.0086 ns | 1.00 |
🌳 Sustainable Code - In Param
Compiled Expressions
In .NET 8, Compiled Expressions are twice as fast as in .NET 7
BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 10 (10.0.19045.3570/22H2/2022Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 8.0.100-rc.2.23502.2
[Host] : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
| Method | Runtime | Mean | Error | StdDev | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio |
|------------ |--------- |--------------:|-------------:|-------------:|---------:|-------:|-------:|----------:|------------:|
| Ex | .NET 7.0 | 234,255.64 ns | 3,023.677 ns | 2,828.350 ns | 3,091.10 | 0.4883 | 0.2441 | 12081 B | 125.84 |
| Ex | .NET 8.0 | 213,176.49 ns | 1,785.515 ns | 1,582.812 ns | 4,648.00 | 0.4883 | 0.2441 | 12145 B | 126.51 |
| | | | | | | | | | |
| Ex_Compiled | .NET 7.0 | 75.84 ns | 0.512 ns | 0.454 ns | 1.00 | 0.0057 | - | 96 B | 1.00 |
| Ex_Compiled | .NET 8.0 | 45.72 ns | 0.910 ns | 0.973 ns | 1.00 | 0.0057 | - | 96 B | 1.00 |
🌳 Sustainable Code - Compiled Expressions
Enumerate List vs. IEnumerable
While List is much faster than IEnumerable as a return value, this difference has diminished in .NET 8.
BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 10 (10.0.19045.3570/22H2/2022Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 8.0.100-rc.2.23502.2
[Host] : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
| Method | Job | Runtime | Mean | Error | StdDev | Gen0 | Allocated |
|------------------------ |--------- |--------- |-----------:|---------:|---------:|-------:|----------:|
| List | .NET 7.0 | .NET 7.0 | 438.3 ns | 8.39 ns | 8.24 ns | - | - |
| List | .NET 8.0 | .NET 8.0 | 436.8 ns | 8.75 ns | 9.36 ns | - | - |
| | | | | | | | |
| List_IEnumerable_ToList | .NET 7.0 | .NET 7.0 | 636.6 ns | 12.54 ns | 14.93 ns | 0.2422 | 4056 B |
| List_IEnumerable_ToList | .NET 8.0 | .NET 8.0 | 599.2 ns | 11.94 ns | 22.73 ns | 0.2422 | 4056 B |
| | | | | | | | |
| IEnumerable_ToList | .NET 7.0 | .NET 7.0 | 1,447.5 ns | 27.83 ns | 29.78 ns | 0.2422 | 4056 B |
| IEnumerable_ToList | .NET 8.0 | .NET 8.0 | 611.3 ns | 12.08 ns | 26.01 ns | 0.2422 | 4056 B |
| | | | | | | | |
| IEnumerable | .NET 7.0 | .NET 7.0 | 2,645.1 ns | 48.93 ns | 48.05 ns | - | 40 B |
| IEnumerable | .NET 8.0 | .NET 8.0 | 532.0 ns | 10.43 ns | 9.76 ns | 0.0019 | 40 B |
| | | | | | | | |
| List_IEnumerable | .NET 7.0 | .NET 7.0 | 4,232.0 ns | 83.24 ns | 89.07 ns | - | 40 B |
| List_IEnumerable | .NET 8.0 | .NET 8.0 | 1,071.4 ns | 20.83 ns | 20.45 ns | 0.0019 | 40 B |
🌳 Sustainable Code - Enumerate List vs. IEnumerable
Small Hotpath Methods
Locale methods are an intelligent way to elicit more performance in the runtime. However, standard methods but also locale methods have improved significantly in terms of performance.
BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 10 (10.0.19045.3570/22H2/2022Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 8.0.100-rc.2.23502.2
[Host] : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
| Method | Runtime | Iterations | Mean | Error | StdDev | Allocated |
|------------- |--------- |----------- |-------------:|-----------:|-----------:|----------:|
| WithoutLocal | .NET 7.0 | 10 | 15.991 ns | 0.1620 ns | 0.1436 ns | - |
| WithoutLocal | .NET 8.0 | 10 | 4.089 ns | 0.0171 ns | 0.0160 ns | - |
| | | | | | | |
| WithLocal | .NET 7.0 | 10 | 6.928 ns | 0.1270 ns | 0.1126 ns | - |
| WithLocal | .NET 8.0 | 10 | 4.894 ns | 0.0300 ns | 0.0266 ns | - |
| | | | | | | |
| WithoutLocal | .NET 7.0 | 100 | 152.300 ns | 0.7867 ns | 0.6569 ns | - |
| WithoutLocal | .NET 8.0 | 100 | 47.345 ns | 0.6740 ns | 0.6304 ns | - |
| | | | | | | |
| WithLocal | .NET 7.0 | 100 | 67.067 ns | 0.3281 ns | 0.3069 ns | - |
| WithLocal | .NET 8.0 | 100 | 47.956 ns | 0.6337 ns | 0.5928 ns | - |
| | | | | | | |
| WithoutLocal | .NET 7.0 | 1000 | 1,504.512 ns | 29.8548 ns | 34.3809 ns | - |
| WithoutLocal | .NET 8.0 | 1000 | 426.727 ns | 4.8606 ns | 4.5467 ns | - |
| | | | | | | |
| WithLocal | .NET 7.0 | 1000 | 644.801 ns | 12.6167 ns | 11.8016 ns | - |
| WithLocal | .NET 8.0 | 1000 | 430.170 ns | 5.7188 ns | 5.3494 ns | - |
🌳 Sustainable Code - Small Hotpath Methods
Pattern Matching value performance
Pattern matching have improved especially when dealing with structs.
BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 10 (10.0.19045.3570/22H2/2022Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 8.0.100-rc.2.23502.2
[Host] : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
| Method | Runtime | Mean |
|---------------------- |--------- |----------:|
| Class_Condition | .NET 7.0 | 0.5471 ns |
| Class_Condition | .NET 8.0 | 0.6678 ns |
| | | |
| Class_Condition | .NET 7.0 | 0.2580 ns |
| Class_Condition | .NET 8.0 | 0.1590 ns |
| | | |
| Class_PatternMatching | .NET 7.0 | 0.2214 ns |
| Class_PatternMatching | .NET 8.0 | 0.0464 ns |
| | | |
| Class_PatternMatching | .NET 7.0 | 0.6593 ns |
| Class_PatternMatching | .NET 8.0 | 0.7481 ns |
| | | |
| Struct_HasValue | .NET 7.0 | 0.2067 ns |
| Struct_HasValue | .NET 8.0 | 0.0358 ns |
| | | |
| Struct_HasValue | .NET 7.0 | 0.0202 ns |
| Struct_HasValue | .NET 8.0 | 0.0259 ns |
| | | |
| Struct_PatterMatching | .NET 7.0 | 0.2331 ns |
| Struct_PatterMatching | .NET 8.0 | 0.0398 ns |
| | | |
| Struct_PatterMatching | .NET 7.0 | 5.0464 ns | => Performance significantly worse
| Struct_PatterMatching | .NET 8.0 | 4.8592 ns | => Performance significantly worse
🌳 Sustainable Code - Pattern Matching value performance
Struct vs Class with Boxing
Boxing as well as general use of interfaces is 30-40% faster in .NET 8 than in .NET 7.
BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 10 (10.0.19045.3570/22H2/2022Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 8.0.100-rc.2.23502.2
[Host] : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.0 (8.0.23.47906), X64 RyuJIT AVX2
| Method | Job | Runtime | Mean | Error | StdDev | Median | Gen0 | Allocated |
|-------------------- |--------- |--------- |----------:|----------:|----------:|----------:|-------:|----------:|
| Class | .NET 7.0 | .NET 7.0 | 3.9827 ns | 0.1245 ns | 0.1332 ns | 3.9282 ns | 0.0014 | 24 B |
| Class | .NET 8.0 | .NET 8.0 | 3.0353 ns | 0.1014 ns | 0.1041 ns | 3.0008 ns | 0.0014 | 24 B |
| | | | | | | | | |
| Struct | .NET 7.0 | .NET 7.0 | 0.0165 ns | 0.0200 ns | 0.0187 ns | 0.0059 ns | - | - |
| Struct | .NET 8.0 | .NET 8.0 | 0.0096 ns | 0.0147 ns | 0.0130 ns | 0.0032 ns | - | - |
| | | | | | | | | |
| ClassWithInterface | .NET 7.0 | .NET 7.0 | 4.0388 ns | 0.1193 ns | 0.1276 ns | 3.9751 ns | 0.0014 | 24 B |
| ClassWithInterface | .NET 8.0 | .NET 8.0 | 3.2790 ns | 0.1087 ns | 0.1787 ns | 3.2270 ns | 0.0014 | 24 B |
| | | | | | | | | |
| StructWithInterface | .NET 7.0 | .NET 7.0 | 4.0907 ns | 0.1203 ns | 0.1338 ns | 4.0992 ns | 0.0014 | 24 B |
| StructWithInterface | .NET 8.0 | .NET 8.0 | 3.3699 ns | 0.1103 ns | 0.1651 ns | 3.3568 ns | 0.0014 | 24 B |
🌳 Sustainable Code - Struct vs Class with Boxing
Conclusion
The performance improvements really pay off, especially in many everyday uses such as lists, string operations, and Linq. Across many everyday samples, we see a performance boost of 30%, which also means that energy is saved on a large scale. Investing in runtime improvements brings something to everyone in .NET 8 - even without customizing code: it comes from the runtime and you get it automatically as a gift during a migration.
With additional smart developers, further performance and thus energy savings can be helped out during development and without additional effort.
.NET 8 makes you faster. Everyday.