【问题标题】:What is the fastest way(s) to loop through a large data chunk on a per-bit basis按位循环遍历大数据块的最快方法是什么
【发布时间】:2009-01-06 21:34:12
【问题描述】:

我正在逐字节地遍历二进制数据的内存块。

目前我正在做这样的事情:

for (i = 0; i < data->Count; i++)
{   
    byte = &data->Data[i];
    ((*byte & Masks[0]) == Masks[0]) ? Stats.FreqOf1++; // syntax incorrect but you get the point.
    ((*byte & Masks[1]) == Masks[1]) ? Stats.FreqOf1++;
    ((*byte & Masks[2]) == Masks[2]) ? Stats.FreqOf1++;
    ((*byte & Masks[3]) == Masks[3]) ? Stats.FreqOf1++;
    ((*byte & Masks[4]) == Masks[4]) ? Stats.FreqOf1++;
    ((*byte & Masks[5]) == Masks[5]) ? Stats.FreqOf1++;
    ((*byte & Masks[6]) == Masks[6]) ? Stats.FreqOf1++;
    ((*byte & Masks[7]) == Masks[7]) ? Stats.FreqOf1++;
}

面具在哪里:

for (i = 0; i < 8; i++)
{
    Masks[i] = 1 << i;
}

(不知何故,我在循环或内联函数中没能做到这么快,所以我把它写出来了。)

有人对如何改进第一个循环有任何建议吗?我对深入浅出相当缺乏经验。

这似乎是一件愚蠢的事情。但我正在实施压缩算法。我只想让位访问部分正确。

谢谢!

PS:这是在 Visual Studio 2008 编译器中。因此,如果建议适用于该编译器,那就太好了。

PPS:我刚刚意识到,我不需要增加两个计数。一个就足够了。然后计算最后的总位数的差异。 但这将特定于仅计数。我真正想要快速完成的是位提取。

编辑: 提出的查找表想法很好。 我意识到我在标题中提出了错误的问题。 因为最终我要做的不是计算位,而是尽可能快地访问每个位。

另一个编辑: 是否可以在数据中仅将指针前移一位?

另一个编辑: 感谢您迄今为止的所有回答。

我想在接下来的步骤中实现一个不分析上下文的简单二进制算术编码器。所以我现在只对单个位感兴趣。最终它将成为一个上下文自适应 BAC,但我会留到以后。

可以选择处理 4 个字节而不是 1 个字节。但是超过 32 位的循环也很昂贵,不是吗?

【问题讨论】:

  • 查看以下链接了解十几个与位相关的内容:Bit Twiddling Hacks
  • 不,您不能将指针前移一位。
  • 我认为您不想将指针前移一位。您希望对 CPU 的字长进行操作,以使事情尽可能地运行。获得 8 位所需的滴答数与 32 位相同......
  • @Paul - 你可以,但它会修改底层数据......好吧,我想它不会真正推进指针,只是将数据移动到指向下一位的指针......无论如何,仍然不推荐......
  • 你能说一下你想对每个位做什么(在访问它们之后)吗?一些上下文可能会有所帮助。

标签: c performance optimization bitmask


【解决方案1】:

最快的方法可能是建立一个字节值与该字节中设置的位数的查找表。至少我在 Google 面试时是这样回答的。

【讨论】:

  • 如果您正在逐字节查看数据,那么只有 256 个可能的值,查找表几乎肯定是您的最佳选择。跨度>
  • 即使您不是逐字节查看,也可以进行掩码并与数据类型中的所有字节进行比较,然后将所有结果相加。
  • 你会因为提供一个谷歌面试问题的答案而被烧死! :P
【解决方案2】:

使用一个表格,将每个字节值 (256) 映射到其中的 1 的数量。 (0 的 # 只是(8 - 1 的 #))。然后遍历字节并对每个字节执行一次查找,而不是多次查找和比较。例如:

int onesCount = 0;
for (i = 0; i < data->Count; i++)
{   
    byte = &data->Data[i];
    onesCount += NumOnes[byte];
}
Stats.FreqOf1 += onesCount;
Stats.FreqOf0 += (data->Count * 8) - onesCount;

【讨论】:

    【解决方案3】:

    我真的不明白你想做什么。但是,如果您只想访问位图的位,您可以使用这些(未经测试!!!)函数:

    #include <stddef.h>
    
    _Bool isbitset(unsigned char * bitmap, size_t idx)
    {
        return bitmap[idx / 8] & (1 << (idx % 8)) ? 1 : 0;
    }
    
    void setbit(unsigned char * bitmap, size_t idx)
    {
        bitmap[idx / 8] |= (1 << (idx % 8));
    }
    
    void unsetbit(unsigned char * bitmap, size_t idx)
    {
        bitmap[idx / 8] &= ~(1 << (idx % 8));
    }
    
    void togglebit(unsigned char * bitmap, size_t idx)
    {
        bitmap[idx / 8] ^= (1 << (idx % 8));
    }
    

    编辑:好的,我认为我明白你想要做什么:在位序列上快速迭代。因此,我们不想使用上面的随机访问函数,而是一次读取整个字的数据。

    您可以使用任何您喜欢的无符号整数类型,但您应该选择一种可能与您的体系结构的字长相对应的类型。我会从stdint.huint_fast32_t 一起去:

    uint_fast32_t * data = __data_source__;
    for(; __condition__; ++data)
    {
        uint_fast32_t mask = 1;
        uint_fast32_t current = *data;
        for(; mask; mask <<= 1)
        {
            if(current & mask)
            {
                // bit is set
            }
            else
            {
                // bit is not set
            }
        }
    }
    

    从内部循环中,您可以设置位

    *data |= mask;
    

    取消设置位
    *data &= ~mask;
    

    并用

    切换位
    *data ^= mask;
    

    警告:代码在大端架构上可能会出现意外行为!

    【讨论】:

      【解决方案4】:

      您可以使用预先计算的查找表,即:

      static int bitcount_lookup[256] = { ..... } ; /* or make it a global and compute the values in code */
      
      ...
      
      for( ... ) 
         byte = ... 
         Stats.FreqOf1 += bitcount_lookup[byte];
      

      【讨论】:

        【解决方案5】:

        这里是一个计算32位整数的1位的方法(基于Java的Integer.bitCount(i)方法):

        unsigned bitCount(unsigned i) {
            i = i - ((i >> 1) & 0x55555555);
            i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
            i = (i + (i >> 4)) & 0x0f0f0f0f;
            i = i + (i >> 8);
            i = i + (i >> 16);
            return i & 0x3f;
        }
        

        因此,您可以将数据转换为 int 并以 4 个字节的步长向前移动。

        【讨论】:

          【解决方案6】:

          这是一个简单的,我只使用一个 32 位值,但你可以看到它并不难适应任何数量的位......

          int ones = 0;
          int x = 0xdeadbeef;
          for(int y = 0;y < 32;y++)
          {
              if((x & 0x1) == 0x1) ones++;
              x = (x >> 1);
          }
          
          printf("%x contains %d ones and %d zeros.\n", x, ones, 32-ones);
          

          但是请注意,它会修改过程中的值。如果您要对需要保留的数据执行此操作,则需要先对其进行复制。

          在 __asm 中执行此操作可能会更好,或许更快,但很难说编译器可以优化到什么程度...

          对于您考虑的每个解决方案,每个解决方案都会有缺点。查找表或移位器(如我的)都有缺点。

          拉里

          【讨论】:

            【解决方案7】:

            ttobiass - 请记住,您的内联函数在您所说的应用程序中很重要,但是您需要记住一些事情。您可以从内联代码中获得性能,只需记住几件事。

            • 调试模式下的内联不存在。 (除非你强迫它)
            • 编译器将内联它认为合适的函数。通常,如果您告诉它内联一个函数,它可能根本不会这样做。即使你使用 __forceinline。有关内联的更多信息,请查看 MSDN。
            • 甚至只能内联某些函数。例如,您不能内联递归函数。

            您将从 C/C++ 语言的项目设置以及构建代码的方式中获得最佳性能。此时,了解堆与堆栈操作、调用约定、内存对齐等很重要。

            我知道这并不能完全回答您的问题,但您提到了性能,以及如何获得最佳性能,这些都是关键。

            【讨论】:

            • 感谢您的帮助。我也注意到了调试行为,所以我切换到了发布模式。我意识到内联函数(即使是由编译器内联)并不能解决所有问题,或者需要付出一些代价。
            【解决方案8】:

            加入链接车: counting bits

            【讨论】:

              【解决方案9】:

              如果这不是过早优化的情况,并且您确实需要挤出最后的每一飞秒,那么您最好使用一个 256 元素的静态数组,用每个字节值的位数填充一次,那么

              Stats.FreqOf1 += bitCountTable[字节]

              当循环完成时:

              Stats.FreqOf0 = ((data->Count * 8) - Stats.FreqOf1)

              【讨论】:

              • 我有点确定它可以算作过早的优化(请参阅我的编辑),因为任务甚至不一样。我只是想把位访问部分放在右边。
              【解决方案10】:

              Beautiful Code 一书中有一整章介绍了不同的技术。您可以在 Google 图书starting here 上阅读(大部分)。

              【讨论】:

                【解决方案11】:

                一种更快的提取位的方法是使用:

                bitmask= data->Data[i];
                
                while (bitmask)
                {
                    bit_set_as_power_of_two= bitmask & -bitmask;
                    bitmask&= bitmask - 1;
                }
                

                如果您只想计算设置的位,缓存中的 LUT 会很快,但您也可以使用the link in this answer 中的交错位计数方法在恒定时间内完成。

                【讨论】:

                  猜你喜欢
                  • 2011-07-17
                  • 1970-01-01
                  • 2019-11-20
                  • 2021-11-07
                  • 2023-03-29
                  • 2010-11-03
                  • 2011-12-11
                  • 1970-01-01
                  相关资源
                  最近更新 更多