简体   繁体   English

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

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

Is it possible in C# to turn a bool into a byte or int (or any integral type, really) without branching ?在 C# 中是否可以将bool转换为byteint (或任何整数类型,真的)而无需分支

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.我们可能会说我们想将bool重新解释为底层byte ,最好用尽可能少的指令。 The purpose is to avoid branch prediction fails as seen here .目的是避免分支预测失败,如此处所示

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

Another option is System.Runtime.CompilerServices.Unsafe , which requires a NuGet package on non-Core platforms:另一个选项是System.Runtime.CompilerServices.Unsafe ,它需要非核心平台上的 NuGet package :

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

The usual C# equivalent to "reinterpret cast" is to define a struct with fields of the types you want to reinterpret.通常的 C# 等效于“重新解释强制转换”是定义一个struct ,其中包含要重新解释的类型的字段。 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.请注意,您只需初始化变量一次,然后您可以根据需要设置Bool和检索Byte 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 .重要的是要指出,如果您可以将bool读为byte ,那么当然任何人都可以将boolbyte ,并且booltrue时的实际int值可能是也可能不是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.既是因为无法保证true的价值实际上是什么样子,也是因为增加了复杂性。 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.我强烈建议不要做这样的事情,直到你有一个被证明的、可重现的现实世界的问题,并且已经用尽了其他更惯用和可维护的选项。

Here is a solution that takes more lines (and presumably more instructions) than I would like, but that actually solves the problem directly, ie by reinterpreting.这是一个解决方案,它比我想要的需要更多的行(并且可能是更多的指令),但它实际上直接解决了问题,即通过重新解释。

Since .NET Core 2.1, we have some reinterpret methods available in MemoryMarshal .从 .NET Core 2.1 开始,我们在MemoryMarshal中提供了一些重新解释方法。 We can treat our bool as a ReadOnlySpan<bool> , which in turn we can treat as a ReadOnlySpan<byte> .我们可以将bool视为ReadOnlySpan<bool> ,反过来我们可以将其视为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

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.基于每个发射的微软文档。

  1. load the parameter in memory (the boolean)加载 memory 中的参数(布尔值)
  2. load in memory a value of int = 0在 memory 中加载 int = 0 的值
  3. compare if any the parameter is greater than the value (branching here maybe?)比较参数是否大于值(可能在这里分支?)
  4. return 1 if true else 0如果为真则返回 1 否则为 0

line 2 and 3 can be removed but the return value could be something else than 0 / 1可以删除第 2 行和第 3 行,但返回值可能不是 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"就像我在开头所说的那样,这段代码取自另一个响应,这似乎工作正常,但在进行基准测试时似乎很慢,查找.net DynamicMethod slow找到使其“更快”的方法

you could maybe use the .GetHashCode of the boolean?您可以使用boolean的 .GetHashCode 吗?

true will return int of 1 and false 0 true 将返回 int of 1 和 false 0

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM