扫描位流中位模式的最快方法

37 c c++ embedded algorithm assembly

我需要在比特流中扫描16位字.不保证在字节或字边界上对齐.

实现这一目标的最快方法是什么?有各种蛮力方法; 使用表和/或移位,但是有没有"bit twiddling shortcuts"可以通过给出yes/no /也可以包含每个字节或单词到达时的标志结果来减少计算次数?

C代码,内在函数,x86机器代码都很有趣.

Toa*_*oad 25

我认为prealc所有移位的单词值并将它们放入16个整数中,这样你就得到了这样的数组

 unsigned short pattern = 1234;
 unsigned int preShifts[16];
 unsigned int masks[16];
 int i;
 for(i=0; i<16; i++)
 {
      preShifts[i] = (unsigned int)(pattern<<i);  //gets promoted to int
      masks[i] = (unsigned int) (0xffff<<i);
 }
Run Code Online (Sandbox Code Playgroud)

然后对于每个unsigned short,你离开流,做一个short和前一个short的int,并将unsigned int与16个unsigned int进行比较.如果其中任何一个匹配,你就得到一个.

所以基本上是这样的:

  int numMatch(unsigned short curWord, unsigned short prevWord)
  {
       int numHits = 0;
       int combinedWords = (prevWord<<16) + curWord;

       int i=0;
       for(i=0; i<16; i++)
       {
             if((combinedWords & masks[i]) == preShifsts[i]) numHits++;
       }
       return numHits;
  }
Run Code Online (Sandbox Code Playgroud)

编辑:请注意,当在同一位上多次检测到模式时,这可能意味着多次命中:

例如32位的0和你要检测的模式是16 0,那么这意味着模式被检测到16次!

  • 凉。如果您使用SIMD,则可以通过将模式“几次彼此相邻”放入巨大的寄存器中,从而可能甚至更快地进行搜索。这样,您可以同时比较类似4 unsigned int的值。 (2认同)
  • 迈克尔:我认为您不能完全理解逻辑。16位模式被转换16次为16个无符号整数,从而产生16位字的所有可能变体。通过一次获取16位并屏蔽它并针对16种可能的模式对其进行检查,就可以完成所需的所有检查。 (2认同)

Who*_*ver 17

如果两个字符{0,1}的字母表上的Knuth-Morris-Pratt算法和reinier的想法都不够快,那么这是一个加速搜索速度32倍的技巧.

您可以首先使用包含256个条目的表来检查位流中的每个字节(如果它包含在您要查找的16位字中).你得到的表

unsigned char table[256];
for (int i=0; i<256; i++)
  table[i] = 0; // initialize with false
for (i=0; i<8; i++)
  table[(word >> i) & 0xff] = 1; // mark contained bytes with true
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用在比特流中找到匹配的可能位置

for (i=0; i<length; i++) {
  if (table[bitstream[i]]) {
    // here comes the code which checks if there is really a match
  }
}
Run Code Online (Sandbox Code Playgroud)

由于256个表条目中最多有8个不为零,因此平均而言,您必须仔细查看每个第32个位置.只有这个字节(结合前一个字节和后一个字节),你才能使用位操作或一些掩盖技术,如reinier建议的那样,看是否有匹配.

该代码假定您使用小端字节顺序.字节中的位的顺序也可能是一个问题(已经实现CRC32校验和的每个人都知道).


Vad*_*ath 10

我想建议使用3个大小为256的查找表的解决方案.这对于大比特流是有效的.该解决方案在样本中占用3个字节用于比较.下图显示了3字节中16位数据的所有可能排列.每个字节区域都以不同的颜色显示.

替代文字http://img70.imageshack.us/img70/8711/80541519.jpg

在第一个样本中检查1到8,在下一个样本中检查9到16,依此类推.现在,当我们搜索模式时,我们将找到此模式的所有8种可能的排列(如下所示),并将存储在3个查找表(左,中,右)中.

初始化查找表:

让我们以一个例子0111011101110111作为模式来寻找.现在考虑第4种安排.左边的部分是XXX01110.用left part(XXX01110)填充左查找表的所有原始数据00010000.1表示输入模式的排列的起始位置.因此,跟随8个原始的左查找表将由16(00010000)填充.

00001110
00101110
01001110
01101110
10001110
10101110
11001110
11101110
Run Code Online (Sandbox Code Playgroud)

安排的中间部分将是11101110.原始指向中间查找表中的索引(238)将由16(00010000)填充.

现在正确的安排部分将是111XXXXX.具有索引的所有原始(32个原始)111XXXXX将由16(00010000)填充.

我们不应该在填充时覆盖查找表中的元素.而是执行按位OR运算来更新已填充的原始数据.在上面的例子中,第3种排列所写的所有原始数据将按第7种排列更新如下.

替代文字http://img527.imageshack.us/img527/8807/76437426.jpg

因此,XX011101在左查找表和11101110中查找表以及111XXXXX右查找表中具有索引的原始数据将被更新为00100010第7个排列.

搜索模式:

取三个字节的样本.查找计数如下,其中左边是查找表,中间是中间查找表,右边是右查找表.

Count = Left[Byte0] & Middle[Byte1] & Right[Byte2];
Run Code Online (Sandbox Code Playgroud)

Count中的1 个数给出了采样样本中匹配模式的数量.

我可以提供一些经过测试的示例代码.

初始化查找表:

    for( RightShift = 0; RightShift < 8; RightShift++ )
    {
        LeftShift = 8 - RightShift;

        Starting = 128 >> RightShift;

        Byte = MSB >> RightShift;

        Count = 0xFF >> LeftShift;

        for( i = 0; i <= Count; i++ )
        {
            Index = ( i << LeftShift ) | Byte;

            Left[Index] |= Starting;
        }

        Byte = LSB << LeftShift;

        Count = 0xFF >> RightShift;

        for( i = 0; i <= Count; i++ )
        {
            Index = i | Byte;

            Right[Index] |= Starting;
        }

        Index = ( unsigned char )(( Pattern >> RightShift ) & 0xFF );

        Middle[Index] |= Starting;
    }
Run Code Online (Sandbox Code Playgroud)

搜索模式:

数据是流缓冲区,左边是左查找表,中间是中间查找表,右边是右查找表.

for( int Index = 1; Index < ( StreamLength - 1); Index++ )
{
    Count = Left[Data[Index - 1]] & Middle[Data[Index]] & Right[Data[Index + 1]];

    if( Count )
    {
        TotalCount += GetNumberOfOnes( Count );
    }
}
Run Code Online (Sandbox Code Playgroud)

局限性:

上面的循环不能检测模式,如果它被放置在数据流缓存器的最末端.以下代码需要在循环后添加以克服此限制.

Count = Left[Data[StreamLength - 2]] & Middle[Data[StreamLength - 1]] & 128;

if( Count )
{
    TotalCount += GetNumberOfOnes( Count );
}
Run Code Online (Sandbox Code Playgroud)

优点:

该算法仅采用N-1个逻辑步骤来查找N个字节数组中的模式.最初只需要填充查询表,这在所有情况下都是常量.因此,这对于搜索大量字节流非常有效.

  • 伟大的解决方案仍然存在.顺便说一句,你的名字听起来很棒.可能是星球大战电影中角色的名字; ^) (2认同)
  • 我可以看到两种优化。首先是先检查中间位置,然后再左右查找,如果中间位置看起来不好,则跳过左右检查。第二种优化是使用一个256x32位表而不是三个256x8表。这将用一个长字引用替换三个一个字节的取消引用(匹配公式为(byte2_lookup &gt;&gt; 16)&(byte1_lookup &gt;&gt; 8)&(byte0_lookup))。请注意,如果查找值为零,则只要发现一个非零查找值就准备回溯就可以跳过一个字节。 (2认同)

小智 9

我的钱在Knuth-Morris-Pratt上,有两个字符的字母.

  • 这表现不佳,因为KMP依赖于向前跳过.但是,当搜索10 .....时,你不能跳过任何东西.当你遇到11 ......时,你有一个不匹配,但字符串可能是110 ....所以必须检查搜索字符串中的第二个1对照你的模式的前一个.如果您的模式是例如11111 .....它确实很好用; 每当遇到0时,你可以提前跳过5.如果字母表很大,KMP效果最好,例如Unicode.对于KMP来说,双字符字母是最糟糕的情况(但是更多的搜索算法会受到小字母的影响) (5认同)
  • 这对他的问题来说太过分了,因为KMP算法适用于任意长的字,其中他的字具有16位的固定长度.KMP对于这个问题太灵活,并且不如简单方法那么有效. (5认同)
  • "两个字符的字母表"表示您将字节流转换为比特流. (3认同)
  • 单词 knuth 自动将分数添加到答案中。;^) 我认为他们的算法与位匹配无关。 (2认同)

mou*_*iel 7

我将实现一个具有16个状态的状态机.

每个状态表示有多少接收位符合该模式.如果下一个接收的位符合模式的下一位,则机器进入下一个状态.如果不是这种情况,则机器退回到第一状态(或者如果模式的开始可以与较少数量的接收位匹配,则返回到另一状态).

当机器到达最后一个状态时,这表示已在比特流中识别出模式.

  • 这太慢了! (2认同)