【问题标题】:C# reinterpret bool as byte/int (branch-free)C# 将 bool 重新解释为 byte/int(无分支)
【发布时间】:2020-03-15 00:41:27
【问题描述】:

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

换句话说,这不够

var myInt = myBool ? 1 : 0;

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

【问题讨论】:

  • 我要补充一点,2 条目 Dictionary<bool, byte> 感觉有点矫枉过正。对于我们正在尝试做的事情来说,基于哈希的查找似乎很重要。
  • “目的是避免分支预测失败,如此处所示” -- 你是否有任何证据表明你确实需要 这样做? IE。你有一个测量性能问题,你已经明确确认是100%由所描述的分支预测问题引起的?底线:您可以使用不安全的代码进行强制转换,但您必须内联执行,否则方法调用可能会导致与错过分支一样多的开销,即使那样您仍然必须处理无法保证的问题什么值存储在bool 中。您不能依赖 1 来获得 true 值。
  • @PeterDuniho 实际上,我没有对 true 值做出任何假设,除非它们是非零的。至于测量:在我看来,链接的问题及其接受的答案很好地涵盖了这一点。否则,您可能会将其视为理论练习,只要将其应用于特定场景即可进行测量。假设当分支预测失败被确定为一个问题时,我希望有一个可用的解决方案。是的,这需要非常高效的内联内容!
  • “需要非常高效的内联内容” -- 并非如此。大多数分支预测问题可以通过重构数据来解决。在我看来,您的推车至少比马领先两步(没有实际问题,并且您已经决定了您认为需要什么解决方案,即使您没有要衡量的问题) . :) 也就是说,我已经在下面的回答中解决了避免bool 测试的最简单有效的方法。
  • @PeterDuniho 虽然完全采取不同路线的建议很好也很受欢迎,但我认为有空间解决有趣的问题是件好事,即使它们可以在某些或所有情况下被规避。跨度>

标签: c# int boolean reinterpret-cast branch-prediction


【解决方案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>));

        }
    }
}

基于每个发射的 microsoft 文档。

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

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

就像我在开头所说的,这段代码取自另一个响应,这似乎工作正常,但在进行基准测试时似乎很慢,查找 .net DynamicMethod slow 以找到使其“更快”的方法

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

true 将返回 int of 1 和 false 0

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

【讨论】:

  • 我喜欢这个主意!但是,唉,我检查了参考来源:实现是return (m_value)?True:False;
  • @Timo,我已经用替代解决方案更新了我的答案:-)
  • 你的新解决方案让我笑了。那太奢侈了!而且确实解决了问题!您能否就相关操作码的作用以及您使用这些操作码的原因添加更多解释?
  • 我似乎找不到明确的答案,但似乎OpCodes.Cgt_Un 分支。 :(
【解决方案2】:

通常的 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,而实际的@当booltrue 时,其值987654330@ 可能为1,也可能不是1。从技术上讲,它可以是任何非零值。

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

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

【讨论】:

  • 同意警告,但仍然是一个非常有趣的解决方案。我对StructLayout(LayoutKind.Explicit) 很熟悉,但我没想到它可以解决这个特殊问题。 :) 如果我们可以控制初始布尔值,那就太好了。如果没有,那我们还得把它们复制到这个结构体中,虽然很可惜,但确实消除了分支!
  • 同意(并意识到)false 为 0,true 为非零(不是 1 本身)。
  • 也许这对Unsafe.As&lt;bool, BoolByte&gt;(ref myBool); 更有用?
  • @BrunoZell 我真的很喜欢!这突然使它成为反对我自己的解决方案的真正竞争者。特别是不必重新解释 两次 或执行索引操作 感觉 更好......虽然它可能只是性能方面的相同。进行基准测试会很有趣。不利的一面是,我们需要在某处定义结构,而不是纯粹依赖系统类型。
【解决方案3】:
unsafe
{
     byte myByte = *(byte*)&myBool;   
}

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

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

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

【讨论】:

  • 出于好奇,是否可以在不声明我们的方法和项目不安全的情况下使用Unsafe.As
  • @Timo 是的,因为它不涉及任何指针类型(S.R.CS.Unsafe 上的一些其他方法)。然而,仅仅因为它不需要“不安全”编译器标志并不能使其安全,它仍然是可能被滥用以违反内存安全的无法验证的代码。到目前为止,已针对此问题发布的所有其他答案也是如此。
  • 完美。很高兴知道。实际上,我相信在 ReadOnlySpan&lt;bool&gt; 上使用 MemoryMarshal.AsBytes 不能被滥用以违反内存安全,因为元素的大小完全相同并且不能变异!
  • @Timo MemoryMarshal.CreateReadOnlySpan 不验证 length 参数,但可能会造成良好的旧缓冲区溢出。
  • 好吧,好吧,如果有人愚蠢到可以通过 >1 长度,那么你是对的。 :p
【解决方案4】:

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

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

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

【讨论】:

    猜你喜欢
    • 2018-08-06
    • 1970-01-01
    • 2020-02-03
    • 2020-05-30
    • 2010-10-17
    • 2013-01-15
    • 2012-02-05
    • 2023-03-21
    • 2022-01-15
    相关资源
    最近更新 更多