以每比特为基础循环遍历大数据块的最快方法是什么

时间:2009-01-06 21:34:12

标签: c performance optimization bitmask

我正按字节顺序运行二进制数据的内存块。

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

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位的循环也是昂贵的,不是吗?

12 个答案:

答案 0 :(得分:16)

最快的方法可能是构建一个字节值查找表与该字节中设置的位数。至少那是我在Google采访时的答案。

答案 1 :(得分:12)

请参阅以下链接,了解十几个相关内容:Bit Twiddling Hacks

答案 2 :(得分:5)

使用将每个字节值(256)映射到其中1的数字的表。 (0的#只是(8 - 1的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 :(得分:2)

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

static int bitcount_lookup[256] = { ..... } ; /* or make it a global and compute the values in code */

...

for( ... ) 
   byte = ... 
   Stats.FreqOf1 += bitcount_lookup[byte];

答案 4 :(得分:2)

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

#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));
}

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

您可以使用任何您喜欢的无符号整数类型,但您应该选择一个可能与您的体系结构的字大小相对应的整数类型。我将使用uint_fast32_t中的stdint.h

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;

警告:代码可能会在big-endian架构上出现意外行为!

答案 5 :(得分:1)

这是一个如何计算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 :(得分:1)

这是一个简单的问题,我只用了一个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 :(得分:1)

ttobiass - 请记住,在您正在谈论的应用程序中,内联函数非常重要,但有些事情需要牢记。你 CAN 从内联代码中获得性能,只记得几件事。

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

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

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

答案 8 :(得分:0)

加入旅行车: counting bits

答案 9 :(得分:0)

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

  

Stats.FreqOf1 + = bitCountTable [byte]

当循环完成时:

  

Stats.FreqOf0 =((data-&gt; Count * 8) - Stats.FreqOf1)

答案 10 :(得分:0)

Beautiful Code一书中,有一整章关于这方面的不同技巧。您可以在Google图书starting here上阅读(大部分)图片。

答案 11 :(得分:0)

提取比特的更快方法是使用:

bitmask= data->Data[i];

while (bitmask)
{
    bit_set_as_power_of_two= bitmask & -bitmask;
    bitmask&= bitmask - 1;
}

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