Deep dive into .NET 11 Performance Improvements #311/06/2026 | 4 minutes to read | Open in Markdown
In this series, I explore some of the most interesting .NET 11 performance improvements described in Stephen Toub's blog post, Performance Improvements in .NET 11.
Each of my post focuses on one example from the original article while digging a little deeper into an additional aspect that was not covered there. The series covers the following topics:
- Allocation free numbers
- Virtual generic method calls
- Current post: Vector zero equality
- Guid creation on Linux
- JIT generating implicit-else assembly instructions
- Guid equality
- Copy-on-write locking strategy
Vector zero equality
When computing with vector register, one of the common operation is to extract a bit from each element of a vector data. This operation uses the ExtractMostSignificantBits method to check whether any lane matches. This method can be combined with Popcnt or Lzcnt instructions to count matching lanes or locate a matching lane. The original post says that, in .NET 11, this pattern is recognized on Arm64, avoiding the need to materialize the full scalar mask; instead, the vector mask can be reduced horizontally.
public class Benchmarks { private Vector128<int> _value = Vector128.Create(1, -2, 3, -4); [Benchmark] public bool AnyLessThanExtract() => AnyLessThanCoreExtract(_value, 0); [Benchmark] public bool AnyLessThanEquality() => AnyLessThanCoreEquality(_value, 0); [MethodImpl(MethodImplOptions.NoInlining)] private static bool AnyLessThanCoreExtract(Vector128<int> value, int limit) => Vector128.LessThan(value, Vector128.Create(limit)) .ExtractMostSignificantBits() != 0; [MethodImpl(MethodImplOptions.NoInlining)] private static bool AnyLessThanCoreEquality(Vector128<int> value, int limit) => Vector128.LessThan(value, Vector128.Create(limit)) != Vector128<int>.Zero; }
This is the optimized assembly code for the AnyLessThanCoreExtract method on Arm64 with .NET 11:
G_M000_IG01: ;; offset=0x0000
stp fp, lr, [sp, #-0x10]!
mov fp, sp
G_M000_IG02: ;; offset=0x0008
dup v16.4s, w0
cmgt v16.4s, v16.4s, v0.4s
umaxv s16, v16.4s
umov w0, v16.s[0]
cmp w0, #0
cset x0, ne
G_M000_IG03: ;; offset=0x0020
ldp fp, lr, [sp], #0x10
ret lr
; Total bytes of code 40
In my measurements on a Snapdragon X 12-core X1E80100 processor running Windows 11, I was not able to measure a significant performance difference before and after the .NET 11 optimization. The optimized code is smaller, which is better for the instruction cache. However, I had two further questions to explore:
- Does x64 with AVX2 also recognize similar patterns?
- Is it worth extracting the most significant bits for a zero comparison, or is it better to compare with a zero vector?
This is the optimized code for AnyLessThanCoreExtract method on x64 with AVX2 using .NET 11:
; Benchmarks.AnyLessThanCoreExtract(System.Runtime.Intrinsics.Vector128`1, Int32)
vmovd xmm0,edx
vpbroadcastd xmm0,xmm0
vpcmpgtd xmm0,xmm0,[rcx]
vmovmskps eax,xmm0
test eax,eax
setne al
movzx eax,al
ret
; Total bytes of code 26
In the x64 case, the assembly code follows a similar pattern and uses vmovmskps to move the mask bits into a scalar register. This takes a single instruction, whereas on Arm64 one instruction reduces the mask to the first lane of the vector register and a second moves that lane's value into a scalar register. It is worth noting that the x64 code is smaller.
It is difficult to measure the performance of such a small code sample accurately. In most cases, computer clocks are not granular enough for these types of measurements, and other operations can dominate the measured overhead. Nevertheless, in my measurements on comparable Arm64 and x64 laptops, the x64 laptop appeared to underperform the Arm64 laptop:
| Method | Mean | Error | StdDev | |-------------------- |---------:|---------:|----------:| | AnyLessThanExtract | 4.162 ns | 5.696 ns | 0.3122 ns | | AnyLessThanEquality | 3.583 ns | 4.849 ns | 0.2658 ns |
Finally, is it worth extracting the significant bits for a comparison? In my x64 measurements, it did not appear to be, but as shown above, the results are extremely noisy. For what it is worth, the code for the AnyLessThanCoreEquality method is one byte shorter, at only 25 bytes.
Conclusion
This post focused on the third of many interesting nuances I learned from the Performance Improvements in .NET 11 article. It explores how .NET 11 optimizes vector comparisons against zero on Arm64 with SVE and x64 with AVX2, including the use of horizontal reduction and vmovmskps. Although the x64 equality based approach produces slightly smaller code and appeared faster in the measurements, the benchmark results were too noisy to establish a meaningful performance difference.