Sustainable Code with .NET 9

Sustainable Code with .NET 9

Sustainable Code is a constantly growing GitHub repository created by me, in which I collect various everyday code snippets and measure the performance of the different implementation ways.
The goal is to create a collection of code that virtually everyone has in front of them every day and can thus easily implement the best way for themselves and their use case.

I took the current release of .NET 9 as an opportunity to update all the examples accordingly and compare them between .NET 7, .NET 8 and - if the dependencies have already been updated - .NET 9.

Here are my rough findings of some of the comparison snippets.

Condition vs Exception 📊

The goal here is to show why exceptions are not a suitable way to control application logic - and that it is advisable to avoid exceptions as much as possible, as recommended by the C# Coding Guidelines.

BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5131/22H2/2022Update)
AMD Ryzen 9 9950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 9.0.100
  [Host]   : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 7.0 : .NET 7.0.20 (7.0.2024.26716), X64 RyuJIT AVX2
  .NET 8.0 : .NET 8.0.11 (8.0.1124.51707), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 9.0 : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI


| Method    | Runtime  | Mean         | Error     | StdDev    | Ratio    | RatioSD | Gen0   | Allocated |
|---------- |--------- |-------------:|----------:|----------:|---------:|--------:|-------:|----------:|
| Condition | .NET 7.0 |    11.495 ns | 0.0733 ns | 0.0612 ns |    10.30 |    0.06 |      - |         - |
| Condition | .NET 8.0 |     1.299 ns | 0.0041 ns | 0.0034 ns |     1.16 |    0.00 |      - |         - |
| Condition | .NET 9.0 |     1.116 ns | 0.0035 ns | 0.0031 ns |     1.00 |    0.00 |      - |         - |
| Exception | .NET 7.0 | 2,336.653 ns | 9.1249 ns | 8.5355 ns | 2,092.87 |    9.28 | 0.0114 |     232 B |
| Exception | .NET 8.0 | 2,303.988 ns | 2.6537 ns | 2.0719 ns | 2,063.61 |    5.80 | 0.0114 |     232 B |
| Exception | .NET 9.0 | 1,072.247 ns | 8.2707 ns | 7.3317 ns |   960.38 |    6.84 | 0.0134 |     224 B |

We can see here that exceptions have become significantly more efficient with .NET 9 - but we are still talking about thousand times slower behavior compared to a condition.

Full sample here.

Any vs Count > 0 📊

This example is about showing how Count vs. Any() works in terms of performance.

BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5131/22H2/2022Update)
AMD Ryzen 9 9950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 9.0.100
  [Host]   : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 7.0 : .NET 7.0.20 (7.0.2024.26716), X64 RyuJIT AVX2
  .NET 8.0 : .NET 8.0.11 (8.0.1124.51707), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 9.0 : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI


| Method | Runtime  | Items | Mean      | StdDev    | Ratio | RatioSD |
|------- |--------- |------ |----------:|----------:|------:|--------:|
| Any    | .NET 7.0 | 0     | 1.9150 ns | 0.0271 ns | 4.069 |    0.06 |
| Any    | .NET 8.0 | 0     | 1.8727 ns | 0.0139 ns | 3.979 |    0.04 |
| Any    | .NET 9.0 | 0     | 0.4706 ns | 0.0031 ns | 1.000 |    0.01 |
|        |          |       |           |           |       |         |
| Count  | .NET 7.0 | 0     | 0.0034 ns | 0.0017 ns | 0.007 |    0.00 |
| Count  | .NET 8.0 | 0     | 0.0038 ns | 0.0018 ns | 0.008 |    0.00 |
| Count  | .NET 9.0 | 0     | 0.0161 ns | 0.0019 ns | 0.034 |    0.00 |
|        |          |       |           |           |       |         |
| Any    | .NET 7.0 | 1     | 1.9256 ns | 0.0194 ns |  4.06 |    0.05 |
| Any    | .NET 8.0 | 1     | 1.8670 ns | 0.0057 ns |  3.94 |    0.02 |
| Any    | .NET 9.0 | 1     | 0.4740 ns | 0.0027 ns |  1.00 |    0.01 |
|        |          |       |           |           |       |         |
| Count  | .NET 7.0 | 1     | 0.0082 ns | 0.0029 ns |  0.02 |    0.01 |
| Count  | .NET 8.0 | 1     | 0.0062 ns | 0.0013 ns |  0.01 |    0.00 |
| Count  | .NET 9.0 | 1     | 0.0189 ns | 0.0051 ns |  0.04 |    0.01 |
|        |          |       |           |           |       |         |
| Any    | .NET 7.0 | 10    | 1.9292 ns | 0.0199 ns |  4.09 |    0.04 |
| Any    | .NET 8.0 | 10    | 1.8612 ns | 0.0050 ns |  3.95 |    0.02 |
| Any    | .NET 9.0 | 10    | 0.4717 ns | 0.0015 ns |  1.00 |    0.00 |
|        |          |       |           |           |       |         |
| Count  | .NET 7.0 | 10    | 0.0107 ns | 0.0051 ns |  0.02 |    0.01 |
| Count  | .NET 8.0 | 10    | 0.0056 ns | 0.0035 ns |  0.01 |    0.01 |
| Count  | .NET 9.0 | 10    | 0.0159 ns | 0.0026 ns |  0.03 |    0.01 |
|        |          |       |           |           |       |         |
| Any    | .NET 8.0 | 100   | 1.8619 ns | 0.0126 ns |  3.93 |    0.05 |
| Any    | .NET 7.0 | 100   | 1.9241 ns | 0.0252 ns |  4.06 |    0.07 |
| Any    | .NET 9.0 | 100   | 0.4740 ns | 0.0055 ns |  1.00 |    0.02 |
|        |          |       |           |           |       |         |
| Count  | .NET 7.0 | 100   | 0.0102 ns | 0.0029 ns |  0.02 |    0.01 |
| Count  | .NET 8.0 | 100   | 0.0058 ns | 0.0071 ns |  0.01 |    0.01 |
| Count  | .NET 9.0 | 100   | 0.0157 ns | 0.0026 ns |  0.03 |    0.01 |

Count is still the much faster variant, but with .NET 9 there was a real performance boost for Linq.

Full sample here.

Small Hotpath Methods 📊

In the hotpath comparison - I am a fan of local methods and their better readability and performance benefit - there was no relevant improvement with .NET 9.

BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5131/22H2/2022Update)
AMD Ryzen 9 9950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 9.0.100
  [Host]   : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 7.0 : .NET 7.0.20 (7.0.2024.26716), X64 RyuJIT AVX2
  .NET 8.0 : .NET 8.0.11 (8.0.1124.51707), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 9.0 : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI


| Method       | Runtime  | Iterations | Mean         | Error      | StdDev     | Ratio |
|------------- |--------- |----------- |-------------:|-----------:|-----------:|------:|
| WithoutLocal | .NET 7.0 | 10         |    11.076 ns |  0.1264 ns |  0.1121 ns |  3.94 |
| WithoutLocal | .NET 8.0 | 10         |     2.791 ns |  0.0315 ns |  0.0295 ns |  0.99 |
| WithoutLocal | .NET 9.0 | 10         |     2.812 ns |  0.0181 ns |  0.0151 ns |  1.00 |
|              |          |            |              |            |            |       |
| WithLocal    | .NET 7.0 | 10         |     3.494 ns |  0.0859 ns |  0.0990 ns |  1.24 |
| WithLocal    | .NET 8.0 | 10         |     2.807 ns |  0.0147 ns |  0.0130 ns |  0.99 |
| WithLocal    | .NET 9.0 | 10         |     2.827 ns |  0.0277 ns |  0.0246 ns |  1.00 |
|              |          |            |              |            |            |       |
| WithoutLocal | .NET 7.0 | 100        |   119.236 ns |  1.5937 ns |  1.4907 ns |  4.91 |
| WithoutLocal | .NET 8.0 | 100        |    25.734 ns |  0.1918 ns |  0.1794 ns |  1.06 |
| WithoutLocal | .NET 9.0 | 100        |    24.262 ns |  0.2096 ns |  0.1960 ns |  1.00 |
|              |          |            |              |            |            |       |
| WithLocal    | .NET 7.0 | 100        |    40.118 ns |  0.8185 ns |  0.8758 ns |  1.64 |
| WithLocal    | .NET 8.0 | 100        |    25.077 ns |  0.3515 ns |  0.3288 ns |  1.03 |
| WithLocal    | .NET 9.0 | 100        |    24.466 ns |  0.2786 ns |  0.2606 ns |  1.00 |
|              |          |            |              |            |            |       |
| WithoutLocal | .NET 7.0 | 1000       | 1,135.122 ns | 17.3263 ns | 16.2071 ns |  4.74 |
| WithoutLocal | .NET 8.0 | 1000       |   250.022 ns |  1.5989 ns |  1.4174 ns |  1.04 |
| WithoutLocal | .NET 9.0 | 1000       |   239.307 ns |  1.5506 ns |  1.4504 ns |  1.00 |
|              |          |            |              |            |            |       |
| WithLocal    | .NET 7.0 | 1000       |   330.232 ns |  5.3309 ns |  4.9866 ns |  1.37 |
| WithLocal    | .NET 8.0 | 1000       |   241.045 ns |  2.0723 ns |  1.9384 ns |  1.00 |
| WithLocal    | .NET 9.0 | 1000       |   241.217 ns |  1.8586 ns |  1.6476 ns |  1.00 |

Full sample here.

Source Generator based Logging Messages 📊

Logging is expensive, so it is advisable to use compile-time logging through Source Code Generators.

BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5131/22H2/2022Update)
AMD Ryzen 9 9950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK 9.0.100
  [Host]   : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 7.0 : .NET 7.0.20 (7.0.2024.26716), X64 RyuJIT AVX2
  .NET 8.0 : .NET 8.0.11 (8.0.1124.51707), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 9.0 : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI


| Method              | Runtime  | Mean      | Error    | StdDev   | Ratio | RatioSD | Gen0   | Allocated |
|-------------------- |--------- |----------:|---------:|---------:|------:|--------:|-------:|----------:|
| SourceCodeGenerated | .NET 7.0 |  83.49 ns | 0.299 ns | 0.279 ns |  1.24 |    0.01 |      - |         - |
| SourceCodeGenerated | .NET 8.0 |  82.12 ns | 0.252 ns | 0.224 ns |  1.22 |    0.01 |      - |         - |
| SourceCodeGenerated | .NET 9.0 |  67.30 ns | 0.365 ns | 0.341 ns |  1.00 |    0.01 |      - |         - |
|                     |          |           |          |          |       |         |        |           |
| Concat              | .NET 7.0 | 112.57 ns | 0.235 ns | 0.208 ns |  1.67 |    0.01 | 0.0286 |     480 B |
| Concat              | .NET 8.0 |  96.94 ns | 1.239 ns | 1.098 ns |  1.44 |    0.02 | 0.0286 |     480 B |
| Concat              | .NET 9.0 |  94.59 ns | 1.136 ns | 1.007 ns |  1.41 |    0.02 | 0.0286 |     480 B |
|                     |          |           |          |          |       |         |        |           |
| Interpolation       | .NET 7.0 | 112.79 ns | 0.444 ns | 0.416 ns |  1.68 |    0.01 | 0.0286 |     480 B |
| Interpolation       | .NET 8.0 |  97.08 ns | 1.208 ns | 1.071 ns |  1.44 |    0.02 | 0.0286 |     480 B |
| Interpolation       | .NET 9.0 |  94.12 ns | 0.620 ns | 0.580 ns |  1.40 |    0.01 | 0.0286 |     480 B |

With .NET 9, this recommended variant has become even faster, making implementation all the more worthwhile, both in terms of memory allocation and performance.

Full sample here.

Summary

.NET 9 is another release that brings with it an enormous performance benefit and thus makes an entire software industry more efficient - for those who rely on .NET.