【问题标题】:Best method to find out set bit positions in a bit mask in C [duplicate]在C中找出位掩码中设置位位置的最佳方法[重复]
【发布时间】:2013-10-14 17:07:59
【问题描述】:

识别 64 位掩码中所有设置位位置的最佳方法是什么。假设我的位掩码是 0xDeadBeefDeadBeef,那么最好的方法是识别其中设置位的所有位位置。

long long bit_mask = 0xdeadbeefdeadbeef;
unsigned int bit_pos=0;
while(mask) {
  if((mask&1)==1) {
     printf("Set bit position is:%d \n",bit_pos};
  }
  bit_pos++;
  mask>>=1; 
}

一种方法是循环遍历它,并检查是否设置了某个位,如果设置了,则返回计数位置并继续循环直到 MSB,因此对于 64 位,我将迭代直到我拥有所有如果设置了 MSB,则设置遍历的位或遍历所有 64 位,但必须有更好的方法吗?

【问题讨论】:

  • 您想要count 设置的位数吗?或者一些二进制打印输出,例如“1101 1110 1010 1101 ...”。
  • 您是否有权访问“计数尾随零”或“popcnt”指令?
  • 您是在数一数,还是对一数的位置感兴趣?
  • 由于提供的示例算法在遇到第一个设置位时停止,我认为这是所需的位置,而不是设置位的总数。考虑到another of the poster's questions 提供的上下文,这也是有意义的。显然需要设置位的位置,因此可以随机选择一个。

标签: c embedded bit-manipulation bitmask bit-masks


【解决方案1】:

Hacker's Delight 中的算法(书籍):

int count_bits(long long s)
{
    s = (s&0x5555555555555555L) + ((s>>1)&0x5555555555555555L);
    s = (s&0x3333333333333333L) + ((s>>2)&0x3333333333333333L);
    s = (s&0x0F0F0F0F0F0F0F0FL) + ((s>>4)&0x0F0F0F0F0F0F0F0FL);
    s = (s&0x00FF00FF00FF00FFL) + ((s>>8)&0x00FF00FF00FF00FFL);
    s = (s&0x0000FFFF0000FFFFL) + ((s>>16)&0x0000FFFF0000FFFFL);
    s = (s&0x00000000FFFFFFFFL) + ((s>>32)&0x00000000FFFFFFFFL);

    return (int)s;
}

【讨论】:

    【解决方案2】:

    除了已经解释了一些不错的小技巧,还有其他选择。

    这假设您有 x86(64)、SSE4、gcc 并使用您可以使用的 -msse4 开关进行编译:

    int CountOnesSSE4(unsigned int x)
    {
        return __builtin_popcount(x);
    }
    

    这将编译成单个 popcnt 指令。如果您需要快速代码,您实际上可以在运行时检查 SSE 并使用可用的最佳功能。

    如果您希望 number 有少量的,这也可能很快(并且总是比通常的移位和比较循环快):

    int CountOnes(unsigned int x)
    {
        int cnt = 0;
        while (x) {
            x >>= ffs(x);
            cnt++;
        }
        return cnt;
    }
    

    在 x86(即使没有 SSE)上,ffs 将编译为单指令 (bsf),循环数取决于指令数。

    【讨论】:

      【解决方案3】:

      你可以这样做:

      long long bit_mask = 0xdeadbeefdeadbeef;
      
      int i;
      for (i = 0; i < (sizeof(long long) * 8); i++) {
          int res = bit_mask & 1;
          printf ("Pos %i is %i\n", i, res);
          bit_mask >>= 1;
      
      }
      

      【讨论】:

      • 你应该编译并运行它,看看你对结果是否满意。
      • 是的,但这意味着,它将遍历所有的 0 和 1,有没有一种方法可以提供更好的方法?
      • 对不起。我修复了我的错误(它现在以正确的方式进行位移,它使用int res 来适应%i)。
      【解决方案4】:

      这取决于您是希望代码清晰还是快速获得结果。我几乎总是在代码中选择清晰性,除非分析另有说明。为清楚起见,您可能会执行以下操作:

      int count_bits(long long value) {
          int n = 0;
          while(value) {
              n += (value & 1);
              value >>= 1;
         }
         return n;
      }
      

      为了提高性能,您可能需要从 X J 的回答中调用 count_bits。

      int count_bits(long long s)
      {
          s = (s&0x5555555555555555L) + ((s>>1)&0x5555555555555555L);
          s = (s&0x3333333333333333L) + ((s>>2)&0x3333333333333333L);
          s = (s&0x0F0F0F0F0F0F0F0FL) + ((s>>4)&0x0F0F0F0F0F0F0F0FL);
          s = (s&0x00FF00FF00FF00FFL) + ((s>>8)&0x00FF00FF00FF00FFL);
          s = (s&0x0000FFFF0000FFFFL) + ((s>>16)&0x0000FFFF0000FFFFL);
          s = (s&0x00000000FFFFFFFFL) + ((s>>32)&0x00000000FFFFFFFFL);
      
          return (int)s;
      }
      

      这取决于您是想查看您的代码并对自己说“是的,这很有意义”还是“我会接受那个人的话”。

      我之前在堆栈溢出中被调用过。有些人不同意。一些非常聪明的人选择复杂而不是简单。我相信干净的代码就是简单的代码。

      如果性能需要,请使用复杂性。如果没有,不要。

      另外,请考虑进行代码审查。当有人说“count_bits 是如何工作的?”时,你会说什么?

      【讨论】:

        【解决方案5】:

        如果您计算的是那些,您可以使用快速的黑客喜悦解决方案,但查找表可以(并不总是)更快。而且更容易理解。您可以预先准备一个表,例如 256 个深度项目,表示字节值 0x00 到 0xFF 的计数

        0, //0x00
        1, //0x01
        1, //0x02
        2, //0x03
        1, //0x04
        2, //0x05
        2, //0x06
        3, //0x07
        ...
        

        构建该表的代码可能会使用逐位方法的缓慢步骤。

        一旦构建,您可以将较大的数字分解为字节

        count  = table8[number&0xFF]; number>>=8;
        count += table8[number&0xFF]; number>>=8;
        count += table8[number&0xFF]; number>>=8;
        count += table8[number&0xFF]; number>>=8;
        ...
        

        如果你有更多的内存,你可以通过表示更宽的数字来使表格更大,一个 65536 深的表格,用于数字 0x0000 到 0xFFFF。

        count  = table16[number&0xFFFF]; number>>16;
        count += table16[number&0xFFFF]; number>>16;
        count += table16[number&0xFFFF]; number>>16;
        count += table16[number&0xFFFF]; number>>16;
        

        表格是一种以消耗内存为代价来加快处理速度的常用方法。您能够消耗的内存越多,您就可以预先计算(在编译时或之前)而不是实时计算。

        【讨论】:

          猜你喜欢
          • 2016-09-24
          • 2021-11-01
          • 1970-01-01
          • 1970-01-01
          • 2020-07-31
          • 1970-01-01
          • 2019-10-21
          • 2015-04-18
          • 2011-07-16
          相关资源
          最近更新 更多