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



是否可以C# 中将bool转换为byteint(或任何整型,真的)而不进行分支?

换句话说,这不够好:

var myInt = myBool ? 1 : 0;

我们可以说我们想将bool重新解释为底层byte,最好是尽可能少的指令。目的是避免分支预测失败,如下所示。

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

另一个选项是System.Runtime.CompilerServices.Unsafe,这需要在非核心平台上使用 NuGet 包:

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

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

通常等效于"重新解释强制转换"的 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,那么当然任何人都可以boolbytebooltrue时的实际int值可能是也可能不是1。从技术上讲,它可以是任何非零值。

综上所述,这将使代码更难维护。这既是因为缺乏对true值实际外观的保证,也是因为增加了复杂性。遇到您询问的错过分支预测问题的真实场景是非常罕见的。即使你有一个合法的现实世界的例子,也可以说它最好用其他方式解决。确切的替代方法将取决于特定的实际示例,但一个例子可能是保持数据组织的方式,允许在给定条件下进行批处理,而不是测试每个元素。

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

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

从 .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

也许这会起作用?(想法来源)

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. 如果为 true,则返回 1,否则返回 0

可以删除第 2 行和第 3 行,但返回值可以是 0/1 以外的其他值

就像我一开始说的,这段代码取自另一个响应,这似乎有效,但在基准测试、查找.net DynamicMethod slow以找到使其"更快"的方法时似乎很慢

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

true 将返回 int 的 1 和 false 0

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

(修改) 来自 System.Convert.ToByte(object) 的源代码:

bool b = true;
byte by = ((IConvertible)b).ToByte(null)

不过我找不到这个的实现,所以我不能保证它不会像 System.Convert 那样执行空检查!

最新更新