.NET 11 Performance Edition

8/1/2026
28 minute read

As always my yearly take on the latest .NET release. In a few months dotnet 11 will be released and I wanted to have a look into some performance metrics.

And I will put some links to the github repo here and run my benchmarks onmmy local machine. Therefore: Mileage may vary. Check against your setup and your own benchmarks. Likely I have errors somewhere! The test setup:

BenchmarkDotNet v0.16.0-preview.1, macOS Sequoia 15.7.7 (24G720) [Darwin 24.6.0]
Apple M2 Pro, 1 CPU, 12 logical and 12 physical cores
Memory: 16 GB Total, 3.48 GB Available
.NET SDK 11.0.100-preview.6.26359.118
  [Host]     : .NET 11.0.0 (11.0.0-preview.6.26359.118, 11.0.26.36018), Arm64 RyuJIT armv8.0-a
  Job-GVKUBM : .NET 10.0.10 (10.0.10, 10.0.1026.32716), Arm64 RyuJIT armv8.0-a
  Job-IHFIKV : .NET 11.0.0 (11.0.0-preview.6.26359.118, 11.0.26.36018), Arm64 RyuJIT armv8.0-a

The following benchmarks are not sorted by anything, just how I discovered them or measured them locally.

Avoid Boxing in Enum.Equals

This PR avoids boxing in Enum.Equals by using the generic Equals method. With that we avoid memory allocations and get a bit of a performance boost. Basically the JIT special-cases that and as a result less assembly code is generated!

public class EnumBenchmarks
{
    private DayOfWeek _left = DayOfWeek.Wednesday;
    private DayOfWeek _right = DayOfWeek.Friday;

    [Benchmark]
    public bool GenericEquals() => AreEqual(_left, _right);

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static bool AreEqual<T>(T left, T right)
        where T : struct, Enum
        => left.Equals(right);
}

Results:

| Method        | Runtime   | Mean     | Error     | StdDev    | Ratio | Gen0   | Code Size | Allocated | Alloc Ratio |
|-------------- |---------- |---------:|----------:|----------:|------:|-------:|----------:|----------:|------------:|
| GenericEquals | .NET 10.0 | 8.390 ns | 0.1348 ns | 0.1195 ns |  1.00 | 0.0057 |     148 B |      48 B |        1.00 |
| GenericEquals | .NET 11.0 | 1.214 ns | 0.0069 ns | 0.0054 ns |  0.14 |      - |      68 B |         - |        0.00 |

Redundant span and null checks

See more information in the runtime-announcement of preview 5. Basically a various spins of removing range checks when clear that they aren't needed. With Span, Vector and friends.

public class RangeCheckBenchmarks
{
    private readonly int[] _numbers =
        [.. Enumerable.Range(0, 1024)];

    [Benchmark]
    public int VectorizedSum()
    {
        ReadOnlySpan<int> data = _numbers;
        Vector128<int> sum = default;

        while (data.Length >= Vector128<int>.Count)
        {
            sum += Vector128.Create(data);
            data = data.Slice(Vector128<int>.Count);
        }

        var result = Vector128.Sum(sum);

        foreach (var value in data)
            result += value;

        return result;
    }
}

Results:

| Method        | Runtime   | Mean        | Error     | StdDev    | Ratio | Code Size |
|-------------- |---------- |------------:|----------:|----------:|------:|----------:|
| VectorizedSum | .NET 10.0 | 128.0731 ns | 0.8132 ns | 0.6791 ns |  1.00 |     168 B |
| VectorizedSum | .NET 11.0 | 115.5956 ns | 0.6213 ns | 0.5811 ns |  0.90 |     128 B |

A rough 10% optimization and smaller code size. Nice one!

TimeZone handling is faster

Let's have a look at this PR. The gist: Timezones are a bitch and a half. There are many rules to go from one timezone (or UTC) to another. Daylight saving time just to mention one. On Linux (I don't know why) are more rules than on Windows/MacOS, so the result there is more "impressive". Anyway every year gets cached by the runtime in terms of its rules and therefore calculations!

public class DateTimeNowBenchmarks
{
    [Benchmark] public DateTime Now() => DateTime.Now;
}

Results:

| Method | Runtime   | Mean     | Error    | StdDev   | Ratio |
|------- |---------- |---------:|---------:|---------:|------:|
| Now    | .NET 10.0 | 34.54 ns | 0.264 ns | 0.234 ns |  1.00 |
| Now    | .NET 11.0 | 17.93 ns | 0.164 ns | 0.153 ns |  0.52 |

50% faster! A-MA-ZING!

Guid is faster

Thanks to this PR - Guid.DecodeByte is now faster. While maybe that doesn't ring a bell to you, it is used by Guid.Parse and friends. So if you parse a lot of GUIDs, you will benefit from this change. The idea is to improve parsing logic of characters to bytes and move failure cases to the end (to have less branching). Especially for UTF-16 chars.

public class GuidBenchmarks
{
    private const string DFormat =
        "00112233-4455-6677-8899-aabbccddeeff";

    private const string NFormat =
        "00112233445566778899aabbccddeeff";

    [Benchmark]
    public Guid ParseD()
        => Guid.Parse(DFormat);

    [Benchmark]
    public Guid ParseN()
        => Guid.Parse(NFormat);

    [Benchmark]
    public Guid ParseExactD()
        => Guid.ParseExact(DFormat, "D");

    [Benchmark]
    public bool TryParseD()
        => Guid.TryParse(DFormat, out _);

    [Benchmark]
    public Guid Constructor()
        => new(DFormat);
}

Results:

| Method      | Runtime   | Mean      | Error     | StdDev    | Ratio |
|------------ |---------- |----------:|----------:|----------:|------:|
| ParseD      | .NET 10.0 | 12.713 ns | 0.1166 ns | 0.1091 ns |  1.00 |
| ParseD      | .NET 11.0 | 10.134 ns | 0.0403 ns | 0.0336 ns |  0.80 |
|             |           |           |           |           |       |
| ParseN      | .NET 10.0 | 11.967 ns | 0.0858 ns | 0.0803 ns |  1.00 |
| ParseN      | .NET 11.0 |  9.485 ns | 0.0561 ns | 0.0498 ns |  0.79 |
|             |           |           |           |           |       |
| ParseExactD | .NET 10.0 | 12.541 ns | 0.1149 ns | 0.1019 ns |  1.00 |
| ParseExactD | .NET 11.0 | 10.093 ns | 0.0867 ns | 0.0811 ns |  0.80 |
|             |           |           |           |           |       |
| TryParseD   | .NET 10.0 | 12.871 ns | 0.0704 ns | 0.0624 ns |  1.00 |
| TryParseD   | .NET 11.0 |  9.612 ns | 0.0216 ns | 0.0181 ns |  0.75 |
|             |           |           |           |           |       |
| Constructor | .NET 10.0 | 12.602 ns | 0.1883 ns | 0.1761 ns |  1.00 |
| Constructor | .NET 11.0 |  9.616 ns | 0.0092 ns | 0.0082 ns |  0.76 |

LINQ Min and Max

Who doesn't like improvements for LINQ? This PR does that. LINQ already uses SIMD (Single Instruction Multiple Data) but you are left with a problem. SIMD is based on Vectors (so continuous slices of memory) but we want a scalar value as a result. So we have to "reduce" the final vector to a given value. Before the PR imagine you have a vector of 8 values to reduce/collapse. The code did go through each of the 8 values and compared them to find the min/max. The new approach does a way more complex thing: Shuffle the vector and then compare it against the original one! Imagine something oversimplified:

vector = Max(vector, ShuffleByHalf(vector));
vector = Max(vector, ShuffleByQuarter(vector));
vector = Max(vector, ShuffleByTwo(vector));
vector = Max(vector, ShuffleAdjacent(vector));

return vector[0];

That works because we are still using SIMD and so as long as the SIMD vector is long enough (so the smaller the datatype) the better. So the following benchmark runs through a few options. To be fair, we use multiple of 2 to make the best use out of SIMD registers. So I do favor SIMD here (as I have a special place in my heart for it).

public class LinqMinMaxBenchmarks
{
    private byte[] _bytes = null!;
    private short[] _shorts = null!;
    private int[] _ints = null!;
    private long[] _longs = null!;

    [Params(16, 64, 1_024)]
    public int Length { get; set; }

    [GlobalSetup]
    public void Setup()
    {
        var random = new Random(42);

        _bytes = new byte[Length];
        _shorts = new short[Length];
        _ints = new int[Length];
        _longs = new long[Length];

        random.NextBytes(_bytes);

        for (var i = 0; i < Length; i++)
        {
            _shorts[i] = (short)random.Next(
                short.MinValue,
                short.MaxValue);

            _ints[i] = random.Next();

            _longs[i] = random.NextInt64();
        }
    }

    [Benchmark]
    public byte MinByte()
        => _bytes.Min();

    [Benchmark]
    public byte MaxByte()
        => _bytes.Max();

    [Benchmark]
    public short MinShort()
        => _shorts.Min();

    [Benchmark]
    public short MaxShort()
        => _shorts.Max();

    [Benchmark]
    public int MinInt()
        => _ints.Min();

    [Benchmark]
    public int MaxInt()
        => _ints.Max();

    [Benchmark]
    public long MinLong()
        => _longs.Min();

    [Benchmark]
    public long MaxLong()
        => _longs.Max();
}

Results:

| Method   | Runtime   | Length | Mean       | Error     | StdDev    | Ratio | RatioSD | Allocated | Alloc Ratio |
|--------- |---------- |------- |-----------:|----------:|----------:|------:|--------:|----------:|------------:|
| MinByte  | .NET 10.0 | 16     |   9.433 ns | 0.0843 ns | 0.0788 ns |  1.00 |    0.00 |         - |          NA |
| MinByte  | .NET 11.0 | 16     |   2.123 ns | 0.0247 ns | 0.0231 ns |  0.23 |    0.00 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MaxByte  | .NET 10.0 | 16     |   8.384 ns | 0.1036 ns | 0.0969 ns |  1.00 |    0.00 |         - |          NA |
| MaxByte  | .NET 11.0 | 16     |   2.126 ns | 0.0355 ns | 0.0332 ns |  0.25 |    0.00 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MinShort | .NET 10.0 | 16     |   6.590 ns | 0.0991 ns | 0.0927 ns |  1.00 |    0.00 |         - |          NA |
| MinShort | .NET 11.0 | 16     |   1.933 ns | 0.0094 ns | 0.0084 ns |  0.29 |    0.00 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MaxShort | .NET 10.0 | 16     |   7.620 ns | 0.1132 ns | 0.1059 ns |  1.00 |    0.00 |         - |          NA |
| MaxShort | .NET 11.0 | 16     |   1.945 ns | 0.0091 ns | 0.0085 ns |  0.26 |    0.00 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MinInt   | .NET 10.0 | 16     |   2.223 ns | 0.0071 ns | 0.0066 ns |  1.00 |    0.00 |         - |          NA |
| MinInt   | .NET 11.0 | 16     |   2.285 ns | 0.0122 ns | 0.0109 ns |  1.03 |    0.01 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MaxInt   | .NET 10.0 | 16     |   2.385 ns | 0.0176 ns | 0.0165 ns |  1.00 |    0.00 |         - |          NA |
| MaxInt   | .NET 11.0 | 16     |   2.268 ns | 0.0056 ns | 0.0052 ns |  0.95 |    0.01 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MinLong  | .NET 10.0 | 16     |   8.542 ns | 0.2339 ns | 0.6824 ns |  1.00 |    0.00 |         - |          NA |
| MinLong  | .NET 11.0 | 16     |   4.753 ns | 0.1149 ns | 0.3351 ns |  0.56 |    0.06 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MaxLong  | .NET 10.0 | 16     |   3.616 ns | 0.0367 ns | 0.0307 ns |  1.00 |    0.00 |         - |          NA |
| MaxLong  | .NET 11.0 | 16     |   4.526 ns | 0.0956 ns | 0.2788 ns |  1.25 |    0.08 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MinByte  | .NET 10.0 | 64     |   8.125 ns | 0.1090 ns | 0.1020 ns |  1.00 |    0.00 |         - |          NA |
| MinByte  | .NET 11.0 | 64     |   2.559 ns | 0.0251 ns | 0.0235 ns |  0.32 |    0.00 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MaxByte  | .NET 10.0 | 64     |  10.418 ns | 0.1050 ns | 0.0982 ns |  1.00 |    0.00 |         - |          NA |
| MaxByte  | .NET 11.0 | 64     |   2.566 ns | 0.0263 ns | 0.0246 ns |  0.25 |    0.00 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MinShort | .NET 10.0 | 64     |   9.139 ns | 0.0930 ns | 0.0870 ns |  1.00 |    0.00 |         - |          NA |
| MinShort | .NET 11.0 | 64     |   6.652 ns | 0.3123 ns | 0.9209 ns |  0.73 |    0.10 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MaxShort | .NET 10.0 | 64     |   8.050 ns | 0.1063 ns | 0.0994 ns |  1.00 |    0.00 |         - |          NA |
| MaxShort | .NET 11.0 | 64     |   6.197 ns | 0.2366 ns | 0.6976 ns |  0.77 |    0.09 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MinInt   | .NET 10.0 | 64     |   6.026 ns | 0.0905 ns | 0.0846 ns |  1.00 |    0.00 |         - |          NA |
| MinInt   | .NET 11.0 | 64     |   5.737 ns | 0.0708 ns | 0.0591 ns |  0.95 |    0.02 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MaxInt   | .NET 10.0 | 64     |   6.046 ns | 0.0709 ns | 0.0663 ns |  1.00 |    0.00 |         - |          NA |
| MaxInt   | .NET 11.0 | 64     |   5.723 ns | 0.0226 ns | 0.0212 ns |  0.95 |    0.01 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MinLong  | .NET 10.0 | 64     |  13.324 ns | 0.2378 ns | 0.2225 ns |  1.00 |    0.00 |         - |          NA |
| MinLong  | .NET 11.0 | 64     |  13.316 ns | 0.0996 ns | 0.0778 ns |  1.00 |    0.02 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MaxLong  | .NET 10.0 | 64     |  13.401 ns | 0.1901 ns | 0.1778 ns |  1.00 |    0.00 |         - |          NA |
| MaxLong  | .NET 11.0 | 64     |  13.370 ns | 0.0971 ns | 0.0908 ns |  1.00 |    0.01 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MinByte  | .NET 10.0 | 1024   |  29.097 ns | 0.4117 ns | 0.3649 ns |  1.00 |    0.00 |         - |          NA |
| MinByte  | .NET 11.0 | 1024   |  23.288 ns | 0.1259 ns | 0.1116 ns |  0.80 |    0.01 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MaxByte  | .NET 10.0 | 1024   |  29.239 ns | 0.1933 ns | 0.1808 ns |  1.00 |    0.00 |         - |          NA |
| MaxByte  | .NET 11.0 | 1024   |  23.447 ns | 0.1377 ns | 0.1220 ns |  0.80 |    0.01 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MinShort | .NET 10.0 | 1024   |  56.559 ns | 0.2093 ns | 0.1958 ns |  1.00 |    0.00 |         - |          NA |
| MinShort | .NET 11.0 | 1024   |  53.602 ns | 0.3492 ns | 0.3095 ns |  0.95 |    0.01 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MaxShort | .NET 10.0 | 1024   |  70.443 ns | 0.1950 ns | 0.1628 ns |  1.00 |    0.00 |         - |          NA |
| MaxShort | .NET 11.0 | 1024   |  53.859 ns | 0.4141 ns | 0.3873 ns |  0.76 |    0.01 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MinInt   | .NET 10.0 | 1024   | 116.909 ns | 0.6835 ns | 0.6393 ns |  1.00 |    0.00 |         - |          NA |
| MinInt   | .NET 11.0 | 1024   | 117.490 ns | 1.3108 ns | 1.2261 ns |  1.00 |    0.01 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MaxInt   | .NET 10.0 | 1024   | 116.267 ns | 0.6059 ns | 0.5668 ns |  1.00 |    0.00 |         - |          NA |
| MaxInt   | .NET 11.0 | 1024   | 115.530 ns | 1.9729 ns | 1.8455 ns |  0.99 |    0.02 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MinLong  | .NET 10.0 | 1024   | 516.667 ns | 2.4785 ns | 2.3184 ns |  1.00 |    0.00 |         - |          NA |
| MinLong  | .NET 11.0 | 1024   | 514.352 ns | 3.6527 ns | 3.4168 ns |  1.00 |    0.01 |         - |          NA |
|          |           |        |            |           |           |       |         |           |             |
| MaxLong  | .NET 10.0 | 1024   | 510.606 ns | 0.4958 ns | 0.4395 ns |  1.00 |    0.00 |         - |          NA |
| MaxLong  | .NET 11.0 | 1024   | 515.495 ns | 2.4118 ns | 2.1380 ns |  1.01 |    0.00 |         - |          NA |

Resources

  • Source code to this blog post: here
  • All my sample code is hosted in this repository: here
An error has occurred. This application may no longer respond until reloaded.Reload x