C# 将 bool 重新解释为 byte/int(无分支)

C# reinterpret bool as byte/int (branch-free)

在 C# 中是否可以将 bool 转换为 byteint(或任何整数类型,真的) 无需分支?

换句话说,这不够:

var myInt = myBool ? 1 : 0;

我们可能会说我们想要将 bool 重新解释为基础 byte,最好使用尽可能少的指令。目的是避免分支预测失败 here.

这是一个解决方案,它比我想要的要多行(可能还有更多的指令),但实际上直接解决了问题,即通过重新解释。

从 .NET Core 2.1 开始,我们在 MemoryMarshal 中提供了一些重新解释方法。我们可以将 bool 视为 ReadOnlySpan<bool>,而后者又可以视为 ReadOnlySpan<byte>。从那里,读取单字节值是微不足道的。

var myBool = true;
var myBoolSpan = MemoryMarshal.CreateReadOnlySpan(ref myBool, length: 1);
var myByteSpan = MemoryMarshal.AsBytes(myBoolSpan);
var myByte = myByteSpan[0]; // =1

也许这行得通? (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>));

        }
    }
}

基于每个发射的微软文档。

  1. 在内存中加载参数(布尔值)
  2. 在内存中加载一个 int = 0 的值
  3. 比较是否有参数大于该值(可能在这里分支?)
  4. return 1 如果为真,否则 0

第 2 行和第 3 行可以删除,但 return 值可能不是 0 / 1

就像我一开始说的,这段代码取自另一个响应,这似乎工作正常,但在进行基准测试时似乎很慢,查找 .net DynamicMethod slow 以找到实现它的方法 "faster"

你也许可以使用布尔值的 .GetHashCode

true 将 return int of 1 和 false 0

然后你可以var myByte = (byte)bool.GetHashCode();

unsafe
{
     byte myByte = *(byte*)&myBool;   
}

另一种选择是System.Runtime.CompilerServices.Unsafe,它需要非核心平台上的 NuGet 包:

byte myByte = Unsafe.As<bool, byte>(ref myBool);

CLI 规范仅将 false 定义为 0 并将 true 定义为 0 以外的任何内容,因此从技术上讲,这可能无法在所有平台上按预期工作。然而,据我所知,C# 编译器还假设 bool 只有两个值,因此在实践中我希望它能在大多数学术案例之外工作。

通常等同于 "reinterpret cast" 的 C# 是定义一个 struct,其中包含您要重新解释的类型的字段。在大多数情况下,这种方法效果很好。在你的情况下,它看起来像这样:

[StructLayout(LayoutKind.Explicit)]
struct BoolByte
{
    [FieldOffset(0)]
    public bool Bool;
    [FieldOffset(0)]
    public byte Byte;
}

然后你可以这样做:

BoolByte bb = new BoolByte();
bb.Bool = true;
int myInt = bb.Byte;

请注意,您只需初始化一次变量,然后您可以根据需要设置 Bool 并检索 Byte。这应该比任何涉及不安全代码、调用方法等的方法表现得一样好或更好,尤其是在解决任何分支预测问题方面。

需要指出的是,如果你能把一个bool读成一个byte,那么当然任何人都可以一个bool 作为 byte,而当 truebool 的实际 int 值可能是也可能不是 1。从技术上讲,它可以是 任何 非零值。

综上所述,这会使代码更难维护。一方面是因为无法保证 true 值实际是什么样子,另一方面是因为增加了复杂性。 运行 进入真实世界的场景是极其罕见的,该场景会遇到您所询问的遗漏分支预测问题。即使你有一个合法的真实世界的例子,也可以说用其他方式更好地解决它是有争议的。确切的替代方案将取决于具体的实际示例,但一个示例可能是以一种允许在给定条件下进行批处理而不是对每个元素进行测试的方式组织数据。

我强烈建议不要做这样的事情,直到你有一个证明的、可重现的现实世界问题,并且用尽了其他更惯用和可维护的选项。