【问题标题】:What is fastest way to convert bool to byte?将布尔转换为字节的最快方法是什么?
【发布时间】:2011-06-26 05:41:41
【问题描述】:

将 bool 转换为 byte 的最快方法是什么?

我想要这个映射:False=0, True=1

注意:我不想使用任何if 语句或其他条件语句。我不希望 CPU 停止或猜测下一条语句。

更新: 对于那些想看到这个问题的重点的人。 这个例子展示了如何从代码中减少两个 if 语句。

byte A = k > 9 ; //If it was possible (k>9) == 0 || 1
c[i * 2] = A * (k + 0x37) - (A - 1) * (k + 0x30);

【问题讨论】:

  • 如果x ? 1 : 0 对你来说真的太慢了​​,那么你真的需要重新检查你想要做什么。如果该级别的转换每秒发生数千万次(它会显着影响性能的唯一方式),那么您为什么不使用 ASM 或其他方式编写它呢?只有这样,您才能在速度上明显击败 x ? 1 : 0
  • 当其中至少有一个(可能有两个)不必要的乘法时,条件“需要优化”有点有趣;-)(但归根结底......它. 只是. 没关系。)另一种方法可能是使用 可以 优化为单跳的开关——不确定是 C# 还是 JIT 做到了。还有一种方法是查找表。呸! :)
  • @Amir:这是完全有效的,而且经常有用,因为其他人指出你在找错树。没有人试图以不专业的方式对待您;你有很多人试图给你建议,这样你就可以 a) 不要把时间浪费在无意义的优化上,b) 更有效地使用这个网站。我们中的许多人都惊讶地看到这个问题被问到,这可能会在我们的 cmets 中遇到。但是,如果您从非防御的角度阅读它们,您会发现没有人侮辱您,只是就您的问题提出建议。这就是 Stack Overflow 的用途。
  • “一个人应该保持沉默并学习” - 投票似乎是另一种选择。
  • @Amir:通常应该保持沉默和学习的人是寻求帮助的人。我们其他人试图提供建设性的信息,如果它是正确的,“不要这样做”建设性的(并且非常有帮助)。如果有人向我展示了一种更好的方法来做某事,我不再关心让旧方法发挥作用了。

标签: c#


【解决方案1】:

怎么样:

byte x = value ? (byte) 1 : (byte) 0;

如果您说的是最有效的方法,可能您可以使用不安全的代码做一些技巧......但这真的是您的瓶颈吗?

编辑:我刚刚意识到条件运算符需要对操作数进行强制转换才能使整个表达式成为一个字节。

编辑:看到您的问题后,有一种 更好 的方式来优化它 IMO。目前,您将执行两种方式都不需要的操作。试试这个:

c[i << 1] = k > 9 ? k + 0x37 : k + 0x30;

c[i << 1] = k + (k > 9 ? 0x37 : 0x30);

(我怀疑哪个没关系。)

您只需要执行比较,然后进行一次加法 - 而不是两次加法和两次乘法从 bool 转换为字节之后。

编辑:刚刚尝试过这个,由于潜在的分支未命中,这仍然肯定比不安全版本慢......或者它可以更快。在 [0, 18) 范围内为 k 选择一个随机值,这种方法花费的时间是不安全代码的两倍。在 [0, 1000) 范围内为 k 选择一个随机值(即一个分支比另一个更频繁地被选择),这种方法比无条件的方法。那么k 值的模式是什么?

这是一些基准代码:

using System;
using System.Diagnostics;

class Test
{
    static void Main()
    {
        Random rng = new Random();
        int[] ks = new int[100000000];
        for (int i = 0; i < ks.Length; i++)
        {
            ks[i] = rng.Next(1000);
        }

        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine("Iteration {0}", i);
            long sum = 0;
            Stopwatch sw = Stopwatch.StartNew();
            for (int j = 0; j < ks.Length; j++)
            {
                int k = ks[j];
                unsafe
                {
                    bool input = k > 9;
                    byte A = *((byte*)(&input)); // 1
                    sum += A * (k + 0x37) - (A - 1) * (k + 0x30);
                }
            }
            sw.Stop();
            Console.WriteLine("Unsafe code: {0}; {1}ms",
                              sum, sw.ElapsedMilliseconds);

            sum = 0;
            sw = Stopwatch.StartNew();
            for (int j = 0; j < ks.Length; j++)
            {
                int k = ks[j];
                sum += k > 9 ? k + 0x37 : k + 0x30;
            }
            sw.Stop();
            Console.WriteLine("Conditional: {0}; {1}ms",
                              sum, sw.ElapsedMilliseconds);
        }
    }
}

请注意,在我的计算机上,这确实sum 提供了相同的值,但我完全不确定它是否保证。我不知道true 的内存表示形式是否有任何保证......所以在某些 CLR 上,您可能会得到错误的答案。

但是,我要指出,在我的笔记本电脑上,这个 1 亿次操作的循环只需要大约 300 毫秒(这包括添加总和和初始数组访问,这很可能需要大量时间,特别是由于缓存未命中)...您真的确定这是瓶颈吗?您希望如何快速获取要散列的数据,从而成为问题?

编辑:我刚刚添加了另一个循环来查看“基本案例”:

for (int j = 0; j < ks.Length; j++)
{
    int k = ks[j];
    sum += k + 0x30;
}

这需要大约一半的时间......所以实际上只有一半的时间花在了特定于哈希的代码上。你真的,真的确定这是一段关键的代码,需要以牺牲可读性和潜在的正确性为代价进行优化?

【讨论】:

  • @Jon 这对我来说是个瓶颈。哈希函数在这里做了两个条件操作。数组长度以 TB 为单位。
  • @Amir - 首先,它不是哈希函数,而是转换函数。其次,您使用的机器有这么多 RAM,不仅可以容纳 TB 大小的字节数组,还可以容纳 UTF-16 编码的十六进制版本(占用的 RAM 是原始的 4 倍)大批)。最重要的是,您仍然在那里制作new string(),它会复制结果字符数组!我什至不会费心解释 Array.Length 属性,它是 32 位的。
  • @Amir:请阅读我更新的答案。你试图优化错误的部分,IMO。
  • @Jon 谢谢你的帮助! “k + (k > 9 ? 0x37 : 0x30)” 是否比“A * (k + 0x37) - (A - 1) * (k + 0x30)” (其中 A=0||1) 快,这里有一些背景: igoro.com/archive/…
  • @Amir:它也可能不再正确......你有多大把握true 总是,总是 以这种方式转换时最终为 1?这似乎仍然不太可能成为真正的瓶颈......它处理数据的速度非常快;您从哪里获取不会使 IO 成为瓶颈的数据?
【解决方案2】:

怎么样

byte x = Convert.ToByte(true);

【讨论】:

  • 你用过反光板吗?即 public static byte ToByte(bool value) { if (!value) { return 0; } 返回 1; }
  • @Vilx-:好吧,OP 不能两全其美。
【解决方案3】:
Convert.ToByte(myBool) 

如果 myBool 为 False,则为 0;如果为 True,则为 1。

【讨论】:

    【解决方案4】:

    使用unsafe 代码这个方法非常快。启用优化后,它比条件运算符快约 30%。

    bool input = true;
    byte value = *((byte*)(&input)); // 1
    

    【讨论】:

    • 这是 Jon 所说的不安全代码,也是要走的路。 +1
    • 这如何适应字节到十六进制的转换?
    • @Henk Holterman:一点也不。喜欢这个问题;)但这是一个很好的答案。
    • 虽然这无疑是一个聪明的解决方案,但我会将任何在生产 C# 代码中实际使用它的开发人员钉死在十字架上,而没有冗长的解释和证明他们如何得出 if 语句和字节转换的结论以违反最小惊讶和维护为代价提供任何可衡量的速度改进。
    • @Jon - 如果您的努力设法说服 @Amir 改变他接受的答案,您将不会收到我的任何抱怨,您甚至可能会得到一些掌声。
    【解决方案5】:
    // Warning! Brain-compiled code ahead!
    static readonly char[] HexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    public static string ToHex(this byte[] me)
    {
        if ( me == null ) return null;
        int ml = me.Length;
        char[] c = new char[2*ml];
    
        int cp = 0;
        for (int i = 0; i < ml; i++ )
        {
            c[cp++] = HexChars[me[i]&15];
            c[cp++] = HexChars[me[i]>>4];
        }
        return new string(c);
    }
    

    【讨论】:

    • &amp;&gt;&gt; 放入小型查找与双完整查找表中会很有趣。特别是如果后者对缓存/位置造成严重破坏。
    • @pst - HexChars 的长度为 32 个字节。我认为它非常适合缓存行。在最坏的情况下,它将占用 2 个缓存行。这里唯一的非局部性是源/目标数组,但它们是以线性方式访问的,所以预取应该像一个魅力一样工作。简而言之,除了一些我一无所知的 SSE 指令之外,我认为这段代码不能进行太多的缓存优化。但这超出了 C# 的范围。
    • 只是一个问题:你不觉得在这样的函数中返回一个字符串很奇怪吗! “terabyte”?
    • @Henk - 也许吧。您确实必须对其进行分析以查看是否有任何区别(我认为 >> 操作可能需要 1 个周期,因此不会有任何明显的区别)。
    • @Homam - 我只修复了 OP 的功能。他后来提到了“太字节”。然后解释说它是循环调用这个函数的结果。 :P
    【解决方案6】:

    以下是比较三个选项的简单基准:

        Int32 j = 0;
        bool b = true;
    
        for (int n = 0; n < 5; n++) {
            Stopwatch sw1 = new Stopwatch();
            Stopwatch sw2 = new Stopwatch();
            Stopwatch sw3 = new Stopwatch();
            sw1.Start();
            for (int i = 100 * 1000 * 1000; i > 0; i--)
                unsafe { j = *(int*)(&b); }
            sw1.Stop();
    
            sw2.Start();
            for (int i = 100 * 1000 * 1000; i > 0; i--)
                j = b ? 1 : 0;
            sw2.Stop();
    
            sw3.Start();
            for (int i = 100 * 1000 * 1000; i > 0; i--)
                j = Convert.ToInt32(b);
            sw3.Stop();
            Trace.WriteLine("sw1: " + sw1.ElapsedMilliseconds +
                "  sw2:" + sw2.ElapsedMilliseconds + ", +" + 100 * (sw2.ElapsedMilliseconds - sw1.ElapsedMilliseconds) / sw1.ElapsedMilliseconds + "% relative to sw1" +
                "  sw3:" + sw3.ElapsedMilliseconds + ", +" + 100 * (sw3.ElapsedMilliseconds - sw1.ElapsedMilliseconds) / sw1.ElapsedMilliseconds + "% relative to sw1"
                );
        }
    

    结果:

    sw1: 172  sw2:218, +26% relative to sw1  sw3:213, +23% relative to sw1
    sw1: 168  sw2:211, +25% relative to sw1  sw3:211, +25% relative to sw1
    sw1: 167  sw2:212, +26% relative to sw1  sw3:208, +24% relative to sw1
    sw1: 167  sw2:211, +26% relative to sw1  sw3:209, +25% relative to sw1
    sw1: 167  sw2:212, +26% relative to sw1  sw3:210, +25% relative to sw1
    

    结论:

    不安全的方法比其他两个快大约 25%!

    “if”版本的相对缓慢是由于分支成本高。 如果 Microsoft 在编译时进行转换,则可以避免转换的成本。

    【讨论】:

      【解决方案7】:

      手写IL:

      .method private hidebysig static 
          int32 BoolToInt (
              bool b
          ) cil managed noinlining 
      {
          .maxstack 8
      
          IL_0000: ldarg.0
          IL_0001: ldc.i4.0
          IL_0002: cgt.un
          IL_0004: ret
      }
      

      而且它们只适用于少数 x86 代码:
      (clrjit.dll 版本 4.7.3131.0)

      test        cl,cl
      setne       al
      movzx       eax,al
      ret
      

      唯一的问题是我没有找到在 C# 中内联 IL 的简单方法。这个答案是使用 dnSpy 完成的。

      【讨论】:

        【解决方案8】:

        你可以使用这个结构来做类似于 ChaosPandion 的解决方案,但使用安全的代码。

        [StructLayout(LayoutKind.Explicit)]
        struct BoolByte
        {
            [FieldOffset(0)]
            public bool flag;
            [FieldOffset(0)]
            public byte num;
        }
        
        ...
        
        bool someBool = true;
        byte num = new BoolByte() { flag = someBool }.num;
        

        我没有对它进行基准测试,所以我不确定速度比较如何。

        [编辑] 好吧,我使用 .NET 3.5 等效单声道运行基准测试,看起来这比普通 if 检查慢了约 10%(在我的 macbook pro 上)。所以忘记这个。我怀疑 .NET 4+ 会在那里有所作为。

        【讨论】:

          【解决方案9】:

          从 .NET Core 2.1 开始,您可以将 bool 重新解释为 byte this way。这是无分支的,应该非常快,因为它几乎不需要“做”任何事情。

          从技术上讲,true 值可以是任何非零的byte,但实际上它是1。这值得考虑。如果您想要绝对确定性,您可以寻找一种有效的、无分支的方式将一个字节转换为1(如果它不为零),否则将其保留为0。 (想到两种方法:A)涂抹位以便所有位都是0 或所有位都是1,然后执行&amp; 1 以获得01。 B) 将0 - n 当作int,这将是零或负数。移动符号位,使其成为最低有效位,从而产生01。)

          【讨论】:

            猜你喜欢
            • 2016-07-04
            • 2016-04-23
            • 2015-05-15
            • 1970-01-01
            • 1970-01-01
            • 2018-04-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多