问题
Is it possible in C# to turn a bool
into a byte
or int
(or any integral type, really) without branching?
In other words, this is not good enough:
var myInt = myBool ? 1 : 0;
We might say we want to reinterpret a bool
as the underlying byte
, preferably in as few instructions as possible. The purpose is to avoid branch prediction fails as seen here.
回答1:
Here is a solution that takes more lines (and presumably more instructions) than I would like, but that actually solves the problem directly, i.e. by reinterpreting.
Since .NET Core 2.1, we have some reinterpret methods available in MemoryMarshal
. We can treat our bool
as a ReadOnlySpan<bool>
, which in turn we can treat as a ReadOnlySpan<byte>
. From there, it is trivial to read the single byte value.
var myBool = true;
var myBoolSpan = MemoryMarshal.CreateReadOnlySpan(ref myBool, length: 1);
var myByteSpan = MemoryMarshal.AsBytes(myBoolSpan);
var myByte = myByteSpan[0]; // =1
回答2:
unsafe
{
byte myByte = *(byte*)&myBool;
}
Another option is System.Runtime.CompilerServices.Unsafe, which requires a NuGet package on non-Core platforms:
byte myByte = Unsafe.As<bool, byte>(ref myBool);
The CLI specification only defines false
as 0
and true
as anything except 0
, so technically speaking this might not work as expected on all platforms. However, as far as I know the C# compiler also makes the assumption that there are only two values for bool
, so in practice I would expect it to work outside of mostly academic cases.
回答3:
maybe this would work? (source of the idea)
using System;
using System.Reflection.Emit;
namespace ConsoleApp10
{
class Program
{
static Func<bool, int> BoolToInt;
static Func<bool, byte> BoolToByte;
static void Main(string[] args)
{
InitIL();
Console.WriteLine(BoolToInt(true));
Console.WriteLine(BoolToInt(false));
Console.WriteLine(BoolToByte(true));
Console.WriteLine(BoolToByte(false));
Console.ReadLine();
}
static void InitIL()
{
var methodBoolToInt = new DynamicMethod("BoolToInt", typeof(int), new Type[] { typeof(bool) });
var ilBoolToInt = methodBoolToInt.GetILGenerator();
ilBoolToInt.Emit(OpCodes.Ldarg_0);
ilBoolToInt.Emit(OpCodes.Ldc_I4_0); //these 2 lines
ilBoolToInt.Emit(OpCodes.Cgt_Un); //might not be needed
ilBoolToInt.Emit(OpCodes.Ret);
BoolToInt = (Func<bool, int>)methodBoolToInt.CreateDelegate(typeof(Func<bool, int>));
var methodBoolToByte = new DynamicMethod("BoolToByte", typeof(byte), new Type[] { typeof(bool) });
var ilBoolToByte = methodBoolToByte.GetILGenerator();
ilBoolToByte.Emit(OpCodes.Ldarg_0);
ilBoolToByte.Emit(OpCodes.Ldc_I4_0); //these 2 lines
ilBoolToByte.Emit(OpCodes.Cgt_Un); //might not be needed
ilBoolToByte.Emit(OpCodes.Ret);
BoolToByte = (Func<bool, byte>)methodBoolToByte.CreateDelegate(typeof(Func<bool, byte>));
}
}
}
based on microsoft documentation of each emit.
- load the parameter in memory (the boolean)
- load in memory a value of int = 0
- compare if any the parameter is greater than the value (branching here maybe?)
- return 1 if true else 0
line 2 and 3 can be removed but the return value could be something else than 0 / 1
like i said in the beginning this code is taken from another response, this seem to be working yes but it seem slow while being benchmarking, lookup .net DynamicMethod slow
to find way to make it "faster"
you could maybe use the .GetHashCode of the boolean?
true will return int of 1 and false 0
you can then var myByte = (byte)bool.GetHashCode()
;
回答4:
The usual C# equivalent to "reinterpret cast" is to define a struct
with fields of the types you want to reinterpret. That approach works fine in most cases. In your case, that would look like this:
[StructLayout(LayoutKind.Explicit)]
struct BoolByte
{
[FieldOffset(0)]
public bool Bool;
[FieldOffset(0)]
public byte Byte;
}
Then you can do something like this:
BoolByte bb = new BoolByte();
bb.Bool = true;
int myInt = bb.Byte;
Note that you only have to initialize the variable once, then you can set Bool
and retrieve Byte
as often as you like. This should perform as well or better than any approach involving unsafe code, calling methods, etc., especially with respect to addressing any branch-prediction issues.
It's important to point out that if you can read a bool
as a byte
, then of course anyone can write a bool
as a byte
, and the actual int
value of the bool
when it's true
may or may not be 1
. It technically could be any non-zero value.
All that said, this will make the code a lot harder to maintain. Both because of the lack of guarantees of what a true
value actually looks like, and just because of the added complexity. It would be extremely rare to run into a real-world scenario that suffers from the missed branch-prediction issue you're asking about. Even if you had a legitimate real-world example, it's arguable that it would be better solved some other way. The exact alternative would depend on the specific real-world example, but one example might be to keep the data organized in a way that allows for batch processing on a given condition instead of testing for each element.
I strongly advise against doing something like this, until you have a demonstrated, reproducible real-world problem, and have exhausted other more idiomatic and maintainable options.
来源:https://stackoverflow.com/questions/58934996/c-sharp-reinterpret-bool-as-byte-int-branch-free