A Pointless Performance Optimisation
Taking a simple, fast function and changing it into a complex, faster function.
I needed a function to output a char
'0'
when called with true
, and '1'
when called with false
. Pretty straightforward:
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static char ToBit(this bool value) => value ? '1' : '0';
Can we make it faster though? I don't really need it to be any faster of course, but that's no reason not to try. The function branches due to the ternary operator - if we could make it branchless then it will probably be faster. Luckily 0 and 1 come after each other in the ASCII table so if we could convert our value to an integer with 0 for false
and 1 for true
then we could simply add it to '0'
. How can we do that though? Unsafe
to the rescue!
A C# bool
is represented under the hood as a byte, with 1 for true
and 0 for false
. Whilst this isn't specified by the C# spec, (I believe) Roslyn does enforce this behaviour, and indeed it's relied on for some JIT optimisations. Armed with that knowledge we can use Unsafe.As
to convert the bool
into a byte
and simply add it to '0'
:
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static char ToBit(this bool value)
{
var @byte = Unsafe.As<bool, byte>(ref value);
return (char)(@byte + '0');
}
How much faster is it though? Here are the results for a benchmark of converting 10 million random bool
values:
| Method | Mean | Error | StdDev | Ratio |
|------------------ |----------:|----------:|----------:|------:|
| Ternary | 34.268 ms | 0.2554 ms | 0.2389 ms | 1.00 |
| Unsafe | 4.068 ms | 0.0539 ms | 0.0450 ms | 0.12 |
Not bad!