Skip to main content

Command Palette

Search for a command to run...

Micro-optimizations in .NET (x86/x64)

Part 1

Updated
3 min read

First of all, I want to thank the developers of SharpLab for providing such an amazing tool to explore the machine code generated by the .NET JIT compiler. All examples in this post are based on .NET 7/8.

Conversion from Boolean to Integer

This is a popular and straightforward example of a micro-optimization. Suppose we want to convert a bool to an int: 1 for true and 0 for false.

The most obvious implementation uses a ternary operator:

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ConvertBooleanToInt(bool value)
{
    return value ? 1 : 0;
}

If you examine the generated machine code, you will see a conditional branch (jne):

; Examples.ConvertBooleanToInt(Boolean)
    L0000: test cl, cl
    L0002: jne short L0007
    L0004: xor eax, eax     ; result = 0
    L0006: ret
    L0007: mov eax, 1       ; result = 1
    L000c: ret

Conditional branches can be expensive due to potential branch mispredictions. However, in the CLI, a bool is physically stored as a 1-byte value (0 for false, 1 for true). We can exploit this by reinterpreting the memory directly.

Using Unsafe Pointers

You can treat the address of the boolean as a pointer to a byte. Note: In your original snippet, return (byte*)&value would return the memory address. To get the value, we must dereference it:

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int ConvertBooleanToIntUnsafe(bool value) 
{
    return *(byte*)&value;
}

The Modern Way: Unsafe.As

A cleaner way to achieve the same result without manual pointer manipulation is using the Unsafe class. This produces the same optimized machine code:

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ConvertBooleanToIntOptimized(bool value) 
{
    return Unsafe.As<bool, byte>(ref value);
}

Generated Machine Code:

; Examples.ConvertBooleanToIntOptimized(Boolean)
    L0000: movzx eax, cl
    L0003: ret

The JIT compiler now uses a single movzx (move with zero-extend) instruction. This completely eliminates the branch, making the code deterministic and faster in tight loops.

Update: Improvements in .NET 9

It is worth noting that starting with .NET 9, the JIT compiler has become much smarter at handling this specific pattern. The community and the Microsoft team implemented an optimization that recognizes the ternary conversion value ? 1 : 0 and automatically transforms it into a branchless movzx instruction.

This means that in .NET 9, the "obvious" version and the "optimized" version now produce identical, high-performance machine code:

; Examples.ConvertBooleanToInt(Boolean) in .NET 9
    L0000: movzx eax, cl
    L0003: ret

Should you still use Unsafe.As?

While .NET 9 handles the simple 1 : 0 case, Unsafe.As or pointer manipulation remains a valuable tool for:

  1. Older Runtime Versions: If your library supports .NET 6, 7, or 8.

  2. Complex Logic: When the mapping isn't a simple 1 or 0, or when you are reinterpreting bool as part of a larger struct layout.

  3. Educational purposes: Understanding how data is represented in memory is key to writing high-performance code.