# Deep dive into .NET 11 Performance Improvements #5

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](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-11).

Each 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:

1. [Allocation free numbers](https://blog.ladeak.net/posts/net11-perf-imp-01.md)
1. [Virtual generic method calls](https://blog.ladeak.net/posts/net11-perf-imp-02.md)
1. [Vector zero equality](https://blog.ladeak.net/posts/net11-perf-imp-03.md)
1. [Guid creation on Linux](https://blog.ladeak.net/posts/net11-perf-imp-04.md)
1. *Current post: [JIT generating implicit-else assembly instructions](https://blog.ladeak.net/posts/net11-perf-imp-05.md)*
1. Guid equality
1. Copy-on-write locking strategy

## JIT generating implicit-else assembly instructions

In [this optimization](https://github.com/dotnet/runtime/pull/124738), the JIT improves if-conversion, which can allow an `if-else` statement to be represented as a conditional operation. This can be useful when a branch is difficult to predict: a misprediction can waste CPU cycles while the processor recovers. A conditional move can avoid a control flow branch, but it is not always faster; performance depends on the surrounding code and the target CPU. Consider this example:

```csharp
private static int ImplicitElse(int left)
{
    int count = 0;
    if (left < 1000)
        count += 1;
    else
        count += -1;
    return count;
}
```

The `ImplicitElse` method is JIT-compiled to the following x64 assembly:

```assembly
; Benchmarks.ImplicitElse(Int32)
       mov       eax,0FFFFFFFF
       mov       edx,1
       cmp       ecx,3E8
       cmovl     eax,edx
       ret
; Total bytes of code 20
```

The `cmp` instruction compares `left` with `0x3E8` (1000 in decimal), and `cmovl` is a *conditional move*. The `eax` register initially holds -1, while `edx` holds 1. If the comparison indicates that `left` is less than 1000, `cmovl` copies the value in `edx` to `eax`; otherwise, `eax` remains -1.

Conditional moves can be useful when branch outcomes are hard to predict, such as when comparing random values. However, the JIT does not always preserve this code shape when the logic is placed in a loop. In my tests, a loop with three iterations was unrolled, producing three `cmovl` instructions. With ten iterations, the generated code changes:

```csharp
private static int ImplicitElse(int left)
{
    int count = 0;
    // Different assembly depending on the iteration limit: 1,3 and 10
    for (int i = 0; i < 10; i++)
    {
        if (left < 1000)
            count += 1;
        else
            count += -1;
    }
    return count;
}
```

The conditional move is replaced by conditional branches and jumps:

```assembly
; Benchmarks.ImplicitElse(Int32)
       xor       eax,eax
       mov       edx,0A
M01_L00:
       cmp       ecx,3E8
       jge       short 0000000000002176
       inc       eax
M01_L01:
       dec       edx
       jne       short 0000000000002167
       ret
M01_L02:
       dec       eax
       jmp       short 0000000000002171
; Total bytes of code 26
```

In this example, `left < 1000` is loop-invariant, so the branch outcome remains the same on every iteration; the disassembly alone does not show that this version is slower.

This could be an opportunity for a future JIT improvement, but how much performance would replacing these branches actually gain? To explore that, I changed the comparison to use `i` instead of 1000:

```csharp
private static int ImplicitElse(int left)
{
    int count = 0;
    for (int i = 0; i < 10; i++)
    {
        if (left < i)
            count += 1;
        else
            count += -1;
    }
    return count;
}
```

Next, I tried using [arithmetic right shifting](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/bitwise-and-shift-operators) to extract the sign bit of the difference. The expression contributes +1 when `left - i` is negative and -1 otherwise:

```csharp
    private static int ImplicitElseNoInlined(int left)
    {
        int count = 0;
        for (int i = 0; i < 10; i++)
            count -= ((left - i) >> 31) | 1;
        return count;
    }
```

> This is not completely equivalent: when `left` is close to `int.MinValue` and `i` is positive, the subtraction can overflow and produce the wrong sign. One can use a `checked` block for correctness, but this expression would still have narrower preconditions.

```assembly
; Benchmarks.ImplicitElseNoInlined(Int32)
       xor       eax,eax
       mov       edx,0A
M01_L00:
       mov       r8d,ecx
       sar       r8d,1F
       or        r8d,1
       sub       eax,r8d
       dec       ecx
       dec       edx
       jne       short 00000000000023E7
       ret
; Total bytes of code 28
```

In my benchmark, this alternative was a few nanoseconds faster for this example. It does not use `cmovl`, and the result does not establish that it will be faster in other cases; I would still be interested in whether C# can express a pattern that reliably leads the JIT to generate a conditional move.

## Conclusion

This post examined how the .NET 11 JIT can generate a conditional move for a simple `if-else`, while emitting branches for similar logic inside a loop. A bit shift based alternative was a few nanoseconds faster in this benchmark, but it is not equivalent for every integer value and the result may not generalize to other workloads.