如何快速扫描完全相等的重复二进制模式的128位组,例如010101 ......或0011001100 ......?
我有多个128位的块,并希望看看它们是否匹配1的数量等于0的数量的模式,例如010101 ......或者00110011 ...或0000111100001111 ...但是不是001001001 ......
问题是模式可能不会在它们的边界上开始,所以模式00110011 ..可以从0110011开始...,并且也将结束1位移位(注意128位不是循环的,所以start不是加入到最后)
010101 ...案例很简单,它只是0xAAAA ......或者0x5555 ....但是随着模式越来越长,排列越来越长。目前我使用重复移位值,例如在这个问题Fastest way to scan for bit pattern in a stream of bits中概述,但更快的东西会很好,因为我在这个例程中占用了所有CPU的70%。其他海报有一般案例的解决方案,但我希望我的模式的对称性可能会导致更优化。
如果它有帮助,我只对长达63位的模式感兴趣,并且最感兴趣的是2种模式(0101 ... 00110011 ... 0000111100001111 ......等)的功能,而5种模式的模式/存在5个零,这些非幂2序列小于0.1%,因此可以忽略它,如果它有助于常见情况更快。
完美解决方案的其他限制是少量的汇编指令,没有疯狂的随机存储器访问(即,大型彩虹表不理想)。
编辑。更精确的图案细节。
我最感兴趣的是0011和0000,1111和0000,0000,1111,1111和16zeros / 1以及32个零/ 1(仅用于逗号的逗号)的模式,其中每个模式在128位内连续重复。对于重复部分而言不是2,4,8,16,32位长的模式不是那么有趣并且可以忽略。 (例如000111 ......)
扫描的复杂性在于图案可能从任何位置开始,而不仅仅是在01或10过渡时。因此,例如,以下所有内容都匹配00001111的4位重复模式...(为了便于阅读,每4位逗号一次)(省略号表示重复相同)
<00> 0000,1111 ....或者0001,1110 ......或者是00111100 ......或者是0111,1000 ......或者1111,0000 ......或者1110,0001 ......或者1100,0011。 ..或1000,0111在128位内,需要重复相同的模式,不存在两种不同的模式。例如,这不是一个有效的模式。 0000,1111,0011,0011 ...因为我们已经从4位重复更改为2位重复。
我已经验证了1的数量是64,这适用于所有的2种模式,现在需要确定重复模式中有多少位(2,4,8,16,32)和多少模式转移了。例如,模式0000,1111是4位模式,移位为0.而0111,1000 ...是4位模式移位3。
答案 0 :(得分:2)
让我们从模式的边界开始的情况开始。您可以检查第一位并使用它来确定您的状态。然后开始循环遍历您的块,检查第一位,递增计数,左移并重复,直到您发现您已经得到了相反的位。您现在可以使用此初始长度作为位集长度。将计数重置为1,然后计算下一组相反的位。切换时,检查长度与初始长度的关系,如果它们不相等则输出错误。这是一个快速的函数 - 它似乎按字符串的预期工作,并且扩展它以处理32字节的块应该不会太难。
unsigned char myblock = 0x33;
unsigned char mask = 0x80, prod = 0x00;
int setlen = 0, count = 0, ones=0;
prod = myblock & mask;
if(prod == 0x80)
ones = 1;
for(int i=0;i<8;i++){
prod = myblock & mask;
myblock = myblock << 1;
if((prod == 0x80 && ones) || (prod == 0x00 && !ones)){
count++;
}else{
if(setlen == 0) setlen = count;
if(count != setlen){
printf("Bad block\n");
return -1;
}
count = 1;
ones = ( ones == 1 ) ? 0 : 1;
}
}
printf("Good block of with % repeating bits\n",setlen);
return setlen;
现在要处理有偏移的块,我建议计算直到第一次'翻转'的位数。存储这个数字,然后运行上面的例程,直到你到达最后一段,其长度不等于其余的集合。将初始位添加到最后一个段的长度,然后您应该能够正确地将其与其余组的大小进行比较。
这段代码非常小,通过缓冲区进行位移不需要在CPU上进行太多工作。我有兴趣看看这个解决方案最终会如何对抗你当前的解决方案。
答案 1 :(得分:1)
针对此类问题的通用解决方案是为模式创建良好的散列函数,并将每个模式存储在散列映射中。一旦为模式创建了哈希映射,然后尝试使用输入流在表中查找。我还没有代码,但是如果您对代码感到震惊,请告诉我。请发布它,我可以继续使用它..
答案 2 :(得分:1)
限制在128流中重复自我的模式使得组合的数量受到限制,并且序列将具有易于检查的属性:
需要反复检查高低部件是否相同;如果它们是对立的,检查该特定长度是否包含连续的长度。
8-bit repeat at offset 3: 00011111 11100000 00011111 11100000
==> high and low 16 bits are the same
00011111 11100000 ==> high and low parts are inverted.
Not same, nor inverted means rejection of pattern.
此时需要检查是否存在一系列 - 向左侧添加“1”并检查它是否为2的幂:n ==(n&amp; -n)是教科书检查的
答案 3 :(得分:1)
我已经考虑过制作一个状态机,所以每个下一个字节(16个中的一个)都会提升它的状态,经过16个状态转换之后你就会识别出这个模式。但这看起来并不乐观。数据结构和逻辑看起来更复杂。
相反,为什么不预先计算所有126个模式(从01到32个零+32个),对它们进行排序并执行二分查找?这将为您提供最多7次迭代的二分搜索。并且您不需要存储每个模式的所有16个字节,因为它的两半是相同的。这为模式数组提供了126 * 16/2 = 1008个字节。您还需要每个模式2个字节的内容来存储零(一)个运行的长度以及相对于您认为未移位的任何模式的移位。这总共是126 *(16/2 + 2)= 1260字节的数据(应该对数据缓存温和)和非常简单的微小二进制搜索算法。基本上,它只是对the answer that you mentioned in the question的改进。
您可能希望在4-5次二进制搜索迭代后尝试切换到线性搜索。这可能会对整个算法产生一点小的推动作用。
最终,获胜者将通过测试/分析确定。这就是你应该做的,得到一些实现并在真实系统中的真实数据上进行比较。