With the annual November release of new .NET versions, Stephen Toub, a Partner Software Engineer on the .NET team, publishes an in-depth analysis of performance improvements around September each year. Given the yearly release cycle, I have decided to update my performance book exclusively for the Long-Term Support (LTS) versions. To complement the book, I will publish articles covering the performance changes in Short-Term Support (STS) versions like .NET 9.
In this article, I will highlight the major performance differences between these two versions.
Performance Improvements & Slowdowns
Below is a brief overview of some key performance enhancements and potential slowdowns introduced in .NET 9. I found some of these results particularly surprising, especially in areas like character handling, integers, doubles, object creation, string comparison, and string compression.
| CATEGORY | DESCRIPTION | PERFORMANCE RESULT |
| Array | All() with a predicate for reference types | Increase of 1.6 times |
| Array | Count() with a predicate for reference types | Increase of 2.28 times |
| Array | foreach() for reference types | Decrease of 1.11 times |
| Array | LongCount() for reference types | Increase of 1.17 times |
| Array | Parallel.For() for value types | Decrease of 1.6 times |
| Array | Parallel.For(): + MaxDegreeOfParallelism for value types | Decrease of 1.56 times |
| Array | Parallel.ForEach() + MaxDegreeOfParallelism for value types | Decrease of 1.6 times |
| Array | Parallel.ForEach() for value types | Decrease of 1.6 times |
| Character | EndsWith() | Decrease of 23.4 times |
| Character | StartsWith() | Increase of 36.75 times |
| Cloning | Reference type | Increase of 1.14 times |
| Cloning | Value type | Increase of 1.12 times |
| Code | Environment.ProcessId | Increase of 1.43 times |
| Code | Null coalescing | Increase of 1.16 times |
| Code | Null coalescing assignment using ?? | Increase of 1.2 times |
| Dictionary | ContainsKey() | Increase of 1.1 |
| Dictionary | Convert to ImmutableDictionary | Increase of 1.4 times |
| Dictionary | Iterating using for() | Increase of 1.24 |
| Exceptions | Trapping exceptions using the when clause | Increase of 2.11 times |
| Exceptions | Trapping exceptions using try/catch | Increase of 2.2 times |
| ImmutableDictionary | Iterating using for() | Increase of 1.68 |
| ImmutableSortedDictionary | Iterating using for() | Increase of 2.44 |
| IQueryable | Any() for reference type | Increase of 2 times |
| JsonSerializer | Deserialize with JsonSerializerContext | Increase of 1.28 times |
| List | All() with a predicate for reference type | Increase of 2 times and allocation decreased by half |
| List | All() with a predicate for value type | Increase of 4.19 times and allocations decreased by almost 100 bytes |
| List | Any() for reference type | Increase of 1.44 times |
| List | Any() for value type | Increase of 2.48 times |
| List | Any() with a predicate for reference type | Increase of 3.14 times and allocations decreased by almost half |
| List | Any() with a predicate for value type | Increase of 1.82 times |
| List | Convert to ImmutableDictionary | Increase of 1.4 times |
| List | Count() for value type | Increase of 2.36 times |
| List | Count() with a predicate for reference type | Increase of 2.43 times and allocations decreased by almost half |
| List | Count() with a predicate for value type | Increase of 4.39 times |
| List | foreach with CollectionsMarshall.AsSpan() for value types | Increase of 1.23 times |
| List | ForEach() for value types | Increase of 1.31 times |
| List | foreach() for value types | Increase of 1.35 times |
| List | Iterating using for() for value types | Increase of 1.28 times |
| List | LINQ First() | Increase of 7.6 times |
| List | LongCount() for value type | Increase of 2.44 times |
| Method | in params | Increase of 1.19 times and decrease of allocations by 15,559 bytes |
| Method | params normal | Increase of 1.21 times and decrease of allocations by 16,119 bytes |
| Method | ref readonly params | Increase of 1.19 times and decrease of allocations by 17,271 bytes |
| Objects | Assigning to a variable for an integer return | Increase of 8.33 times |
| Objects | Create value type | Increase of 1.37 times |
| Objects | CreateInstance() for a value type | Increase of almost 2 times |
| Objects | Creating a public class | Increase of 4 times |
| Objects | Creating a public sealed class | Increase of 5 times |
| Objects | Creating a value object | Increase of 1.63 times |
| Objects | Creating an internal class | Increase of 4 times |
| Objects | Creating an internal sealed class | Increase of 4.44 times |
| Objects | Creating Attribute | Increase of 3.7 times |
| Objects | Creating sealed Attribute | Increase of 3.86 times |
| Objects | Disposing of objects using the new style of using | Increase of 1.2 times |
| Objects | Disposing of objects using the using statement | Increase of 1.18 times |
| Objects | Disposing of objects using try/finally | Increase of 1.17 times |
| Objects | GetUninitializedObject() for reference type | Increase of 4.32 times |
| Objects | GetUnitializedObject for reference type | Increase of 4.32 times |
| Objects | Using discard for integer return | Decrease of 15.3 times |
| Span | Fill() | Decrease of 2.12 times |
| String | Combine 2 strings using Join() | Increase of 1.45 times |
| String | Comparison using AsSpan().Equals(OrdinalIgnoreCase) | Increase of 22 times |
| String | Comparison using Equals() | Decrease of 6.32 times |
| String | Comparison with EndsWith() | Decrease of 1.2 times |
| String | Contains() | Increase of 5.27 times |
| String | EndsWith() | Decrease of 1.29 times |
| String | Replace() with OrdinalIgnoreCase | Increase of 1.22 times |
| String Compression | Using DeflateStream async with CompressionLevel.Fastest | Decrease of 2.27 times |
| String Compression | Using DeflateStream async with CompressionLevel.NoCompression | Decrease of 2 times |
| String Compression | Using DeflateStream async with CompressionLevel.Optimal | Decrease of 2.5 times |
| String Compression | Using DeflateStream async with CompressionLevel.SmallestSize | Decrease of 2.55 times |
| String Compression | Using GZipStream async with CompressionLevel.Fastest | Decrease of 2.25 times |
| String Compression | Using GZipStream async with CompressionLevel.NoCompression | Decrease of 4 times |
| String Compression | Using GZipStream async with CompressionLevel.Optimal | Decrease of 2.5 times |
| String Compression | Using GZipStream async with CompressionLevel.SmallestSize | Decrease of 2.54 times |
| String Compression | Using ZLibStream async with CompressionLevel.Fastest | Decrease of 2.3times |
| String Compression | Using ZLibStream async with CompressionLevel.NoCompression | Decrease of 4.1 times |
| String Compression | Using ZLibStream async with CompressionLevel.Optimal | Decrease of 2.5 times |
| String Compression | Using ZLibStream async with CompressionLevel.SmallestSize | Decrease of 2.6 times |
| String Decoding | Using BigEndianUnicode | Decrease of 1.5 times |
| String Encoding | Using Encoding.UTF32 | Decrease of 1.16 times |
| String Validation | Any() | Increase of 1.94 times |
| String Validation | Using ? and string.Length | Increase of 57 times |
| StringBuilder | Append() | Increase of 1.35 times |
| StringBuilder | AppendFormat() | Increase of 1.19 times |
| Type | Initializing an integer | Decrease of 7.25 times |
| Types | Consuming a constant that is a double | Decrease of 733.75 times |
| Types | Consuming a readonly property that is a double | Increase of 4.69 times |
| Types | Double to integer | Decrease of 3.13 times |
| Types | Double to integer with casting | Decrease of 3.5 times |
| Types | Initialize reference type | Decrease of 1.31 times and increase of allocation by 3,644 bytes |
| Types | Using readonly property that is a double | Decrease of 7.36 times |
Summary
This article highlights only a portion of the performance changes I discovered using the thousands of benchmark tests I routinely run to track .NET improvements. I have focused on performance changes of 1.1 or higher. Overall, based on all the tests I conducted; .NET 9 is approximately 1.11 times faster than .NET 8—a notable improvement for an interim release!
This guide is intended to help you decide whether to upgrade to .NET 9. I strongly recommend upgrading as soon as possible to leverage the enhanced performance. Your users will benefit from the increased speed without any need for changes to your existing code.
Feel free to share any questions or feedback in the comments below.


Pick up any books by David McCarter by going to Amazon.com: http://bit.ly/RockYourCodeBooks
Make a one-time donation
Make a monthly donation
Make a yearly donation
Choose an amount
Or enter a custom amount
Your contribution is appreciated.
Your contribution is appreciated.
Your contribution is appreciated.
DonateDonate monthlyDonate yearlyIf you liked this article, please buy David a cup of Coffee by going here: https://www.buymeacoffee.com/dotnetdave
© The information in this article is copywritten and cannot be preproduced in any way without express permission from David McCarter.
Discover more from dotNetTips.com
Subscribe to get the latest posts sent to your email.
