用于在位阵列中查找字节的高效算法

时间:2015-05-11 18:48:13

标签: c algorithm search

给定一个bytearray uint8_t data[N]什么是在其中找到字节uint8_t search的有效方法即使search不是八位字节对齐?即search的前三位可能位于data[i]中,后一位位于data[i+1]中。

我当前的方法涉及创建一个bool get_bit(const uint8_t* src, struct internal_state* state)函数(struct internal_state包含一个右移位的掩码,&用src并返回,维护size_t src_index < size_t src_len),leftshifting将位返回uint8_t my_register并每次与search进行比较,并使用state->src_indexstate->src_mask获取匹配字节的位置。

有更好的方法吗?

5 个答案:

答案 0 :(得分:4)

如果您要在大型数组中搜索8位模式,则可以实现16位值的滑动窗口,以检查搜索的模式是否是构成该16位值的两个字节的一部分。

为了便携,您必须通过构建16位值手动搜索模式来处理由我的实现完成的字节序问题。高字节始终是当前迭代的字节,低字节是后面的字节。如果您进行简单的转换,例如value = *((unsigned short *)pData),您将在x86处理器上遇到麻烦......

设置value后,设置cmpmask cmp并移动mask。如果在高高字节内未找到模式,则循环继续检查下一个字节为起始字节。

这是我的实现,包括一些调试打印输出(函数返回位位置,如果找不到模式,则返回-1):

int findPattern(unsigned char *data, int size, unsigned char pattern)
{
    int result = -1;
    unsigned char *pData;
    unsigned char *pEnd;
    unsigned short value;
    unsigned short mask;
    unsigned short cmp;
    int tmpResult;



    if ((data != NULL) && (size > 0))
    {
        pData = data;
        pEnd = data + size;

        while ((pData < pEnd) && (result == -1))
        {
            printf("\n\npData = {%02x, %02x, ...};\n", pData[0], pData[1]);

            if ((pData + 1) < pEnd)   /* still at least two bytes to check? */
            {
                tmpResult = (int)(pData - data) * 8;   /* calculate bit offset according to current byte */

                /* avoid endianness troubles by "manually" building value! */
                value = *pData << 8;
                pData++;
                value += *pData;

                /* create a sliding window to check if search patter is within value */
                cmp = pattern << 8;
                mask = 0xFF00;
                while (mask > 0x00FF)   /* the low byte is checked within next iteration! */
                {
                    printf("cmp = %04x, mask = %04x, tmpResult = %d\n", cmp, mask, tmpResult);

                    if ((value & mask) == cmp)
                    {
                        result = tmpResult;
                        break;
                    }

                    tmpResult++;   /* count bits! */
                    mask >>= 1;
                    cmp >>= 1;
                }
            }
            else
            {
                /* only one chance left if there is only one byte left to check! */
                if (*pData == pattern)
                {
                    result = (int)(pData - data) * 8;
                }

                pData++;
            }
        }
    }

    return (result);
}

答案 1 :(得分:2)

我不知道它会不会更好,但我会使用滑动窗口。

uint counter = 0, feeder = 8;
uint window = data[0];

while (search ^ (window & 0xff)){
    window >>= 1;
    feeder--;
    if (feeder < 8){
        counter++;
        if (counter >= data.length) {
            feeder = 0;
            break;
        }
        window |= data[counter] << feeder;
        feeder += 8;
    }
}

//Returns index of first bit of first sequence occurrence or -1 if sequence is not found
return (feeder > 0) ? (counter+1)*8-feeder : -1;

通过一些更改,您可以使用此方法搜索任意长度(1到64-array_element_size_in_bits)位序列。

答案 2 :(得分:2)

我认为你在C中可以做得比这更好:

/*
 * Searches for the 8-bit pattern represented by 'needle' in the bit array
 * represented by 'haystack'.
 *
 * Returns the index *in bits* of the first appearance of 'needle', or
 * -1 if 'needle' is not found.
 */
int search(uint8_t needle, int num_bytes, uint8_t haystack[num_bytes]) {
    if (num_bytes > 0) {
        uint16_t window = haystack[0];

        if (window == needle) return 0;
        for (int i = 1; i < num_bytes; i += 1) {
            window = window << 8 + haystack[i];

            /* Candidate for unrolling: */
            for (int j = 7; j >= 0; j -= 1) {
                if ((window >> j) & 0xff == needle) {
                    return 8 * i - j;
                }
            }
        }
    }
    return -1;
}

主要思想是在更宽的数据类型(在这种情况下为uint16_t)中处理通过配对字节跨越连续字节之间边界的87.5%的情况。你可以调整它以使用更广泛的数据类型,但我不确定它会获得任何收益。

你无法安全或轻易做到的是通过指针(即(uint16_t *)&haystack[i])将部署或全部数组转换为更宽整数类型的任何事情。您无法确保此类强制转换的正确对齐,也不能确保解释结果的字节顺序。

答案 3 :(得分:1)

如果AVX2可以接受(早期版本没有那么好用,但你仍然可以在那里做点什么),你可以同时在很多地方进行搜索。我无法在我的机器上测试这个(只编译),所以以下更多的是让你知道如何处理它而不是复制和粘贴代码,所以我试着解释它而不是只是代码转储。

主要思想是阅读uint64_t,将所有有意义的值(0到7)右移,然后对这8个新uint64_t中的每一个进行测试,测试字节是否在那里。小的复杂性:对于uint64_t移位超过0的情况,不应计算最高位置,因为它有零移位到它可能不在实际数据中。完成此操作后,应在与当前值相差7的位置读取下一个uint64_t,否则会出现未检查的边界。那很好,不对齐的载荷不再那么糟糕,特别是如果它们不宽的话。

现在对于一些(未经测试,不完整,见下文)代码,

__m256i needle = _mm256_set1_epi8(find);
size_t i;
for (i = 0; i < n - 6; i += 7) {
    // unaligned load here, but that's OK
    uint64_t d = *(uint64_t*)(data + i);
    __m256i x = _mm256_set1_epi64x(d);
    __m256i low = _mm256_srlv_epi64(x, _mm256_set_epi64x(3, 2, 1, 0));
    __m256i high = _mm256_srlv_epi64(x, _mm256_set_epi64x(7, 6, 5, 4));
    low = _mm256_cmpeq_epi8(low, needle);
    high = _mm256_cmpeq_epi8(high, needle);
    // in the qword right-shifted by 0, all positions are valid
    // otherwise, the top position corresponds to an incomplete byte
    uint32_t lowmask = 0x7f7f7fffu & _mm256_movemask_epi8(low);
    uint32_t highmask = 0x7f7f7f7fu & _mm256_movemask_epi8(high);
    uint64_t mask = lowmask | ((uint64_t)highmask << 32);
    if (mask) {
        int bitindex = __builtin_ffsl(mask);
        // the bit-index and byte-index are swapped
        return 8 * (i + (bitindex & 7)) + (bitindex >> 3);
    }
}

有趣的&#34;位索引和字节索引被交换&#34;这是因为在qword中的搜索是逐字节完成的,并且这些比较的结果最终在8个相邻位中,而搜索&#34;移位1&#34;在接下来的8位中结束,依此类推。因此在结果掩码中,包含1的字节的索引是位偏移,但该字节中的位索引实际上是字节偏移量,例如0x8000将对应于查找第7个字节的字节。右移1的qword,所以实际指数是8 * 7 + 1.

还有&#34; tail&#34;的问题,当处理完所有7个字节的块时,剩下的部分数据。它可以以相同的方式完成,但现在更多的位置包含伪造的字节。现在剩下n - i个字节,因此掩码必须在最低字节中设置n - i位,并且对于所有其他字节设置少一个(由于与之前相同的原因,其他位置具有零移位)在)。此外,如果只有1个字节&#34;左&#34;,它就不会被遗漏,因为它已经过测试,但这并不重要。我假设数据被充分填充,以便访问越界并不重要。这是未经测试的:

if (i < n - 1) {
    // make n-i-1 bits, then copy them to every byte
    uint32_t validh = ((1u << (n - i - 1)) - 1) * 0x01010101;
    // the lowest position has an extra valid bit, set lowest zero
    uint32_t validl = (validh + 1) | validh;
    uint64_t d = *(uint64_t*)(data + i);
    __m256i x = _mm256_set1_epi64x(d);
    __m256i low = _mm256_srlv_epi64(x, _mm256_set_epi64x(3, 2, 1, 0));
    __m256i high = _mm256_srlv_epi64(x, _mm256_set_epi64x(7, 6, 5, 4));
    low = _mm256_cmpeq_epi8(low, needle);
    high = _mm256_cmpeq_epi8(high, needle);
    uint32_t lowmask = validl & _mm256_movemask_epi8(low);
    uint32_t highmask = validh & _mm256_movemask_epi8(high);
    uint64_t mask = lowmask | ((uint64_t)highmask << 32);
    if (mask) {
        int bitindex = __builtin_ffsl(mask);
        return 8 * (i + (bitindex & 7)) + (bitindex >> 3);
    }
}

答案 4 :(得分:1)

如果您正在搜索大量内存并且可以负担昂贵的设置,另一种方法是使用64K查找表。对于每个可能的16位值,该表存储一个字节,该字节包含匹配八位位组发生的位移偏移(+1,因此0表示不匹配)。您可以像这样初始化它:

uint8_t* g_pLookupTable = malloc(65536);
void initLUT(uint8_t octet)
{
     memset(g_pLookupTable, 0, 65536); // zero out
     for(int i = 0; i < 65536; i++)
     {          
         for(int j = 7; j >= 0; j--)
         {
             if(((i >> j) & 255) == octet)
             {
                 g_pLookupTable[i] = j + 1;
                 break;
             }
         }
     }
}

请注意,不包括值为8位的情况(原因在一分钟内会很明显)。

然后你可以像这样扫描你的字节数组:

 int findByteMatch(uint8_t* pArray, uint8_t octet, int length)
 {
     if(length >= 0)
     {
         uint16_t index = (uint16_t)pArray[0];
         if(index == octet)
             return 0;
         for(int bit, i = 1; i < length; i++)
         {
             index = (index << 8) | pArray[i];
             if(bit = g_pLookupTable[index])
                 return (i * 8) - (bit - 1);
         }
     }
     return -1;
 }

进一步优化:

  • 从pArray一次读取32个或多个位到uint32_t然后移位和AND每个一次获得一个字节,或者使用索引和测试,然后读取另一个4。
  • 通过为每个索引存储nybble将LUT打包为32K。这可能有助于它挤入某些系统的缓存中。

这取决于你的内存架构,这是否比没有使用查找表的展开循环更快。