给定一个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_index
和state->src_mask
获取匹配字节的位置。
有更好的方法吗?
答案 0 :(得分:4)
如果您要在大型数组中搜索8位模式,则可以实现16位值的滑动窗口,以检查搜索的模式是否是构成该16位值的两个字节的一部分。
为了便携,您必须通过构建16位值手动搜索模式来处理由我的实现完成的字节序问题。高字节始终是当前迭代的字节,低字节是后面的字节。如果您进行简单的转换,例如value = *((unsigned short *)pData)
,您将在x86处理器上遇到麻烦......
设置value
后,设置cmp
和mask
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;
}
进一步优化:
这取决于你的内存架构,这是否比没有使用查找表的展开循环更快。