在字节数组中计数0个b位子序列

Counting zero b-bit subsequences in a byte array

本文关键字:0个 字节数 字节 数组      更新时间:2023-10-16

我正在寻找在任意大小的S (S通常很小)的uint8_t数组中计数b位子序列(非重叠)的数量的最快方法。 0 (S通常很小)。

约束条件包括:

  • b总是2的幂,有效值实际上只有:1、2、4、8、16和32
  • 假设uint8_t的位数为8,且S * 8能被b
  • 整除

例子:

  • b = 4, array = 0xA0 0x39 0x04 0x30 -正确答案为3
  • b = 1, array = 0xFF 0x1F 0xF8 -正确答案为6
  • b = 16, array = 0x05 0x16 0x32 0x00 -正确答案为0

我目前正在做的是我"解包"成字节的位,然后memcmp与零缓冲区的子序列,但在我看来,应该有一个更快的方法来做到这一点。

您可以使用类似于检测字符串中的空字节的众所周知的方法的位旋转。例如,对于b=4,您可以读取32位字x并执行

__builtin_popcount((x - 0x11111111) & (~x & 0x88888888))

这里,x - 0x11111111产生一个值,如果4位组为零或已经设置,则每个4位组的高位为1;第二部分丢弃已经设置的部分,然后只计算剩余的位。

对于仅考虑从b位偏移开始的序列的额外约束,有一个非常简单的解决方案(这里也不存在端序性问题,因为您只考虑整个零块):

size_t countZeroChunks(const uint8_t* bytes, size_t nbytes, uint8_t b) {
    assert(b == 2 || b == 4 || b == 8 || b == 16 || b == 32);
    size_t count = 0;
    if(b <= 8) {
        // chunks fit inside a byte
        for(size_t i = 0; i < nbytes; ++i) {
            uint8_t byte = *bytes++;
            for(uint8_t offset = 0; offset < 8; offset += b) {
                // collect bits in chunk
                // e.g. for b=2 at offset=2
                // yyyyxxyy >> 2 -> 00yyyyxx
                // 00yyyyxx << 6 -> xx000000
                uint8_t chunk = (byte >> offset) << ((8 - offset) % 8);
                if(chunk == 0)
                    ++count;
            }
        }
    } else {
        // chunks span multiple bytes
        size_t nchunks = nbytes * 8 / b;
        for(size_t i = 0; i < nchunks; ++i) {
            // collect chunk from bytes
            uint32_t chunk = 0;
            for(size_t k = 0, bytesPerChunk = b / 8; k < bytesPerChunk; ++k)
                chunk |= (uint32_t)(*bytes++) << (k * 8);
            if(chunk == 0)
                ++count;
        }
    }
    return count;
}