是否可以在C# 中将bool
转换为byte
或int
(或任何整型,真的)而不进行分支?
换句话说,这还不够好:
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
定义为0
,true
定义为除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
,那么当然任何人都可以把bool
写成byte
,bool
在true
时的实际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>));
}
}
}
基于每个发出的微软文档。
- 在内存中加载参数(布尔值)
- 在内存中加载值 int = 0
- 比较是否有任何参数大于该值(也许在这里分支?
- 如果为 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 那样执行空检查!