Skip to content
Ian Griffiths By Ian Griffiths Technical Fellow I
How .NET 8.0 boosted AIS.NET performance by 27%

At endjin, we maintain Ais.Net, an open source high-performance library for parsing AIS message (the radio messages that ships broadcast to report their location, speed, etc.). Earlier this year, we reported how .NET 7.0 had given us a 19% performance increase. (And that was on top of the 20% performance boost .NET 6.0 had given us the in the previous year).

Predictably, we've repeated our measurements now that .NET 8.0 has come out, and once again, we got a significant boost. I build my computers to last, so I'm still using the same desktop as when I first started benchmarking this library, meaning these figures are all directly comparable.

Again, this was a free lunch! Without making any changes whatsoever to our code, our benchmarks improved by roughly 27% simply by running the code on .NET 8.0 instead of .NET 7.0. As with last time, we've not had to release a new version—the existing version published on NuGet (which targets netstandard2.0 and netstandard2.1) sees these performance gains the moment you use it in a .NET 8.0 application.

Our memory usage seems to have remained consistent. Our amortized allocation cost per record continues to be 0 bytes. The total memory usage including startup costs has remained about the same at a handful of kilobytes, depending on exactly which features you use.

Benchmark results

We have two benchmarks. One measures the maximum possible rate of processing messages, while doing as little work as possible for each message. This is not entirely realistic, but it's useful because it establishes the upper bound on how fast an application can process AIS messages on a single thread. The second benchmark uses a slightly more realistic workload, inspecting several properties from each message. Each benchmark runs against a file containing one million AIS records.

When I tested on .NET 6.0 in January 2022, I saw the results shown in this next table when running the benchmarks on my desktop. These figures correspond to an upper bound of 3.8 million messages per second, and a processing rate of 3.1 million messages a second for the slightly more realistic example. (My desktop is about 5 years old, and it has an Intel Core i9-9900K CPU.)

Method Mean Error StdDev Allocated
InspectMessageTypesFromNorwayFile1M 262.9 ms 3.21 ms 2.84 ms 5 KB
ReadPositionsFromNorwayFile1M 322.8 ms 4.50 ms 3.99 ms 7 KB

Running the same benchmarks on .NET 7.0 gave us about 4.7 million messages per second for the very basic case and 3.7 million per second for the more realistic one:

Method Mean Error StdDev Allocated
InspectMessageTypesFromNorwayFile1M 213.8 ms 4.25 ms 4.55 ms 4 KB
ReadPositionsFromNorwayFile1M 267.9 ms 3.74 ms 3.31 ms 5 KB

I repeated both these test just now on the very latest .NET 6.0 and .NET 7.0 runtimes, and within the bounds of experimental noise, the results were essentially the same. (That's what you'd hope, given that this is running on the same hardware, but the .NET runtime does get regular updates, so it's worth checking performance has remained the same on those versions.)

Here, then, are the .NET 8.0 numbers:

Method Mean Error StdDev Allocated
InspectMessageTypesFromNorwayFile1M 174.7 ms 2.20 ms 2.06 ms 4 KB
ReadPositionsFromNorwayFile1M 210.5 ms 4.15 ms 4.08 ms 5 KB

This shows that on .NET 8.0, our upper bound moves up to 5.72 million messages per second, while the processing rate for the more realistic example goes up to 4.75 million messages per second. Those are improvements of 22% and 27% respectively on .NET 7.0. (I put the 27% figure in the blog title not because it's the larger number, but because that benchmark better represents what a real application might do. I did the same thing for .NET 7.0 even though with that upgrade, the 2nd benchmark showed the smaller of the two increases.)

The bottom line is that just as moving your application onto .NET 7.0 may well have given you an instant performance boost with no real effort on your part (as did moving to .NET 6.0 before that) you may enjoy a similar boost upgrading to .NET 8.0.

We've been running these benchmarks across 5 versions of .NET now (.NET Core 2, .NET Core 3.1, .NET 6.0, .NET 7.0, and .NET 8.0,) enabling us to visualize how performance has improved across these releases for our library. First we'll look at the time taken to process 1 million AIS messages:

Bar chart showing the time in ms to inspect and read positions from 1 million AIS messages for .NET Core 3.1 (inspect: 329.2, read: 384.2), .NET 6.0 (inspect: 262.9, read: 322.8), .NET 7.0 (inspect: 213.8, read: 267.9), and .NET 8.0 (inspect: 174.7, read: 210.5)

And next, the throughput in AIS messages per second (same benchmarks, just a different way to present the results):

Bar chart showing how many AIS messages can be inspected and positions read per second, for .NET Core 2 (inspect:  2,877,698, read:  2,478,315), .NET Core 3.1 (inspect:  3,037,667, read: 2,602,811), .NET 6.0 (inspect:  3,803,728, read:  3,097,893), .NET 7.0 (inspect:  4,677,268, read:  3,732,736), and .NET 8.0 (inspect: 5,724,093, read: 4,750,594)

Over the time the AIS.NET library has existed, performance has roughly doubled, thanks entirely to improvements in the .NET runtime!

You can learn more about our Ais.Net library at the GitHub repo, http://github.com/ais-dotnet/Ais.Net/ and in the same ais-dotnet GitHub organisation you'll also find some other layers, as illustrated in this diagram:

A diagram showing the Ais.Net library layering as three rows. The top row provides this description of Ais.Net.Receiver: AIS ASCII data stream ingestion using IAsyncEnumerable. Decoded message available via IObservable. The second row provides this description of Ais.Net.Models: Immutable data structures using C# 9.0 Records. Interface expose domain concepts such as position. The third row provides this description of Ais.Net: high performance, zero-allocation decode using Span<T>. ~3 million messages per second per core.

Note that Ais.Net.Models is currently inside the Ais.Net.Receiver repository. If you would like to experiment with this library, you will find some polyglot notebooks illustrating its use at https://github.com/ais-dotnet/Ais.Net.Notebooks

Ian Griffiths

Technical Fellow I

Ian Griffiths

Ian has worked in various aspects of computing, including computer networking, embedded real-time systems, broadcast television systems, medical imaging, and all forms of cloud computing. Ian is a Technical Fellow at endjin, and Microsoft MVP in Developer Technologies. He is the author of O'Reilly's Programming C# 10.0, and has written Pluralsight courses on WPF (and here) and the TPL. He's a maintainer of Reactive Extensions for .NET, Reaqtor, and endjin's 50+ open source projects. Technology brings him joy.