24 comments

[ 1.4 ms ] story [ 51.7 ms ] thread
Amazing progress, some LINQ constructions were made 300 times faster. But reasoning over what code does heap allocations is getting more and more complicated. I guess intuition has to give even more way to benchmarking
My view, which I suspect even Toub would agree with is that if being allocation free or even just extremely low allocation is critical to you, then go ahead and use structure and stackalloc, etc that guarentee no allocations.

It is far more guarenteed that that will work in all circumstances than these JIT optimizations, which could have some edge cases where they won't function as expected. If stopwatch allocations were a major concern (as opposed to just feeling like a possible perf bottleneck) then a modern ValueStopwatch struct that consists of two longs (accumulatedDuration, and startTimestamp, which if non-zero means the watch is running) plus calling into the stopwatch static methods is still simple and unambiguous.

But in cases where being low/no allocation is less critical, but your are still concerned about the impacts of the allocations, then these sort of optimizations certainly do help. Plus they even help when you don't really care about allocations, just raw perf, since the optimizations improve raw performance too.

If only they could fix the ecosystem's stability; I feel like anything written with C#'s staple packages becomes outdated considerably faster than any other options.
Can you provide more examples? I've taken a Win32 application from .NET 3.5, converted it to a .NET Console Application (it was a server with a GUI) to run on .NET 8 with minimal friction, a lot of it wound up me just importing .NET Framework packages anew from NuGet.

What are you looking for out of .NET? The staple packages don't go away as often as in languages like NodeJS

If you are not on the bleeding edge of whatever new framework Microsoft is promoting today, the ecosystem is incredibly stable.
(comment deleted)
This kinda stuff brings languages like C# and Java closer to Rust in performance, thinking like the "borrow checker" it understands the scope of some objects and puts them on the stack and avoids garbage collection and allocation overhead.

It keeps the unsung benefit of garbage collection for "programming in the large" in which memory allocation is treated as a global concern independent of everything else instead of a global concern that has to be managed locally in every line of code.

Rust's strategy is problematic for code reuse just as C/C++'s strategy is problematic. Without garbage collection a library has to know how it fits into the memory allocation strategies of the application as a whole. In general a library doesn't know if the application still needs a buffer and the application doesn't know if the library needs it, but... the garbage collector does.

Sure you can "RC all the things" but then you might as well have a garbage collector.

In the Java world we are still waiting for

https://openjdk.org/projects/valhalla/

Efficient memory allocation is part of a well written designed API.

Languages like C++ give you a tonne of options here, from passing in scratch buffers to libraries, passing in reusable containers, move semantics, to type erased primitives like std::memory_resource and std::shared_ptr

Whenever I read about those yearly performance improvements, I wonder if there are some real world benchmarks. Some applications that were benchmarked on all .NET versions up from Framework 4.7 as a baseline. And all the .NET versions since as a comparison.
This reads like one of those recipe blogs where you first need to hear about great grandpappy's migration during the potato famine before you can get to the details on how to make cupcakes. First 5 paragraphs are just noise.
To be fair there is like 500 paragraphs of content after it
Try to imagine the hours going into a post like this.

These posts are among the very best, digging into details explaining why things work and why changes were made.

Every time they get released I'm happy because no one killed it...

The first five paragraphs tell a very relevant story to drive a key point home: performance is often about many small things shaved down, not one giant silver bullet.

I’ve lost count of the number of times I’ve seen customers immediately “double down” on the size of their servers as a quick fix… and achieving nothing other than increasing their cloud provider’s revenue.

Performance comes from a long series of individually small fixes.

(comment deleted)
Now if only they could bring their focus on performance to windows 11 as a whole.

It’s just shocking how much faster vanilla Linux is compared to vanilla windows 11.

Edit: by vanilla Linux I mean out of the box installation of your typical distribution e.g. Ubuntu without any explicit optimisation or tuning for performance

C# is definitely fast.

There are some benchmark games that I relied on in the past as a quick check and saw it as underwelming vs rust/c++.

For example:

https://benchmarksgame-team.pages.debian.net/benchmarksgame/...

We see that the fastest C# version is 6 times slower than the rust/c++ implementation.

But that's super deceiving because those versions use arena allocators. Doing the same (wrote this morning, actually) yielded a ~20% difference vs the fastest rust implementation.

This was with dotnet 9.

I think the model of using GC by default and managing the memory when it's important is the sanest approach. Requiring everything to be manually managed seems like a waste of time. C# is perfect for managing memory when you need it only.

I like rust syntactically. I think C# is too object-oriented. But with a very solid standard lib, practical design, good tools, and speed when you need it, C# remains super underrated.

This sort of post really makes me appreciate what's gone into the JVM. A lot of these optimizations are things that the JVM has long implemented. That's not a knock on C# either. Besides the JVM the only other place you'll see these sorts of optimizations are the likes of V8.

It makes me happy to see MS investing in C# like this. I love the notion of having competing VMed languages.

I was wondering how the JVM and V8 stack up. Do you have a source for that claim? Genuinely curious.

Coming from the game dev world, I’ve grown more and more convinced that managed languages are the right move for most code. My reasoning is simple: most game developers don’t have the time or patience to deeply understand allocation strategy, span usage, and memory access patterns, even though those are some of the most performance-critical and time-consuming parts of programming to get right.

Managed languages hide a lot of that complexity. Instead of explaining to someone, “you were supposed to use this specialized allocator for your array and make sure your functions were array-view compatible”—something that’s notoriously tedious to guarantee in game engines given how few developers even think about array views—you just let developers write code and most of those problems go away.

I’m not saying everything should be managed. Core engine code should still live in the predictable, statically compiled world. But history shows it can work: projects like Jak and Daxter were written primarily in a custom LISPy scripting language, and even Ryujinx (RIP), the excellent Nintendo Switch emulator, is written entirely in C#.

Another strong technical reason is that managed JIT languages can profile at runtime and keep optimizing call sites based on actual usage patterns. Normally, developers would have to do this by hand or rely on PGO, which works but is painful to set up.

Industry standards make this harder to adopt since platforms like Sony still block JIT, but I think this is the direction we should be moving.

This whole post alone deserves a big kudos. Very detailed, true engineering culture.
Native AOT brings C# closer to Go, pretty nice feature. BTW, Upgrading .NET apps for the last few years has been such a breeze. It won't take more than a few minutes + adjusting a couple build pipelines
The big benefit of this for my work is being able to run sites on smaller and cheaper boxes every year. It's not just the speed, but they've made huge strides in the last couple of versions in massively reducing the memory use of different object types.

This is only being compared to last year's v9, but if you compare against v7 from a couple of years ago, the changes are huge.

And this only reflects the changes to the underlying framework compilation, and doesn't factor in changes to say the Kestrel web server and static asset delivery that have taken a ton of load away.

Intel are also regularly checking in changes before they release new CPUs now so that the framework is ahead of their releases and takes advantage of new features.

Interesting that they've introduced LeftJoin and RightJoin into Linq with this version, 18 years after the 1st version of LinqToSQL with .net 3.5. Left and right joins aren't a particularly obscure thing.
We run a (pretty) big multi-tenant SaaS app on dotnet and I was literally able to downgrade our production servers from 4-core-16GB vms to 2-core-8GB on AWS when going from .NET 6 to .NET 8 (we only use LTS releases b/c compliance, don't ask). Super excited to try .NET 10. Also, almost zero breaking changes when bumping versions, which is very refreshing compared to the front-end world.

That said, while C# (and the dotnet runtime) are awesome, MS is doing it a disservice lately (poor tooling, Cursor/VSCode controversy etc. etc.) C# could've been so much bigger...