Micro-optimizations in .NET (x86/x64)
Part 1
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:
Older Runtime Versions: If your library supports .NET 6, 7, or 8.
Complex Logic: When the mapping isn't a simple
1or0, or when you are reinterpretingboolas part of a larger struct layout.Educational purposes: Understanding how data is represented in memory is key to writing high-performance code.




