Stephen Toub writes the longest performance post of the year, every year, and the framing has not changed: nothing in it is a breakthrough. A bounds check removed, an allocation that stops happening, a lock that is not taken, a loop that runs in fewer cycles. Each one is nearly invisible on its own, and the point is that they are not invisible together.

What is in the .NET 11 edition:

  • Runtime async is the headline — async methods no longer need a compiler-generated state machine allocated on the heap, because suspension and resumption move into the runtime instead of into generated MoveNext code
  • Guarded devirtualization — profile data lets the JIT emit a direct, inlinable call on the hot path and keep the original virtual dispatch as the fallback, so the common case is tiny and correctness survives the rare case
  • Escape analysis keeps trimming false positives, so objects that provably do not escape are stack-allocated instead of heap-allocated — a nullable-boxing helper drops from 9.58 ns with 24 bytes allocated to 1.99 ns with none
  • GC write barriers — array stores in a loop go from 10.85 µs to 6.04 µs once the JIT can see the covariance check and barrier as separate operations and elide them
  • Regex gets a final cleanup pass after whole-pattern optimization, so shared prefixes are factored out and [ab]+c[ab]+|[ab]+ drops from 550.9 ns to 282.8 ns
  • Cryptography — one-shot hashing keeps its state on the stack, and AES key wrap reuses a single native cipher instead of building and destroying one per block
  • Also covered: bounds check elimination, assertion propagation, vectorization, intrinsics, register allocation, and frozen data

The numbers that land hardest are the async ones. A chain of 30 async methods that throws and catches across each layer drops from 65.97 µs and 85.5 KB allocated to 11.25 µs and 7.7 KB, because exceptions no longer get caught and stored into a fresh task at every pass-through frame.

The lessons generalize well beyond .NET:

  • One optimization exposes the next, so the ordering of compiler passes is itself a design problem
  • Fix the runtime and every application inherits the win without recompiling
  • Every claim in the post is a side-by-side BenchmarkDotNet run you can reproduce on your own hardware, including the disclaimer about micro-benchmarks
  • Runtime async does not cover async void, async iterators, or custom task-like return types, and Task versus ValueTask remains an API design decision — the compiler optimizing something is not a reason to stop making deliberate API choices

This is what real performance work looks like when someone writes it all down: mostly dull, individually negligible, measured changes that add up to something you can feel.