7

ビットのテキスト内のビットのパターンを見つける文字列検索アルゴリズムを実装する必要がありました(一致はバイト/ワードで整列されていない可能性があります)。手始めに、ボイヤームーアアルゴリズムを実装しましたが、個々のビットの比較は私の目的には遅すぎました。そこで、代わりに、このペーパーで説明されているようにバイト/ワード全体を比較するブロックベースのバージョンを実装しようとしましたが、複雑になり、管理できなくなりました(一部、自分が何をしているかを完全に理解していないため)。

誰かがそのようなアルゴリズムの良い実装を持っていますか?

私の特定のユースケースは、パターンの長さN >= 32、テキストウィンドウ2N、およびビットがintsにパックされている場合です。この場合もN、charサイズの倍数ですN % 8 == 0。ボイヤームーア文字のように、私は1回前処理し、テキストの変更に何度も使用します。最初の試合は私が必要とするすべてです。パフォーマンスが重要です。

編集: Blocked Boyer-Mooreアルゴリズムの実装に成功した後、改善が見られないことに気付きました(ビットごとのバージョンの方が高速です!)これはおそらく私自身の間違いです。多くのコメント行がなければ意味がありませんが、それでも遅いです。こちらです。

4

3 に答える 3

3

概要

私は SO コミュニティには不慣れですが、何かお返しできることを楽しみにしています。

興味深い問題です。比較で高価なビット操作を実行するのではなく、(事前に計算されたビット パターンとビット マスクを使用して) バイト ベースの比較のみを行う実装をまとめました。その結果、かなり高速になるはずです。これは、 Boyer-Moore アルゴリズムについて説明したシフト ルール (パフォーマンスの最適化) を実装していないため、さらに改善される可能性があります。

この実装はパターン ビット % CHAR_BIT == 0 の数に依存しますが、8 ビット マシンでは N % 8 == 0 の基準を満たしますが、実装は非バイト アライン ビット パターンを検出します。(現在は 8 ビット文字 ( CHAR_BIT == 8 ) も必要ですが、万一システムが 8 ビット文字を使用していない場合は、すべての配列/ベクトルを uint8_t から char に変更し、調整することで簡単に適応できます。それらに含まれる値は、正しいビット数を反映しています。)

検索がビット操作を行わないことを考えると (事前に計算されたバイト マスクを AND 処理する以外に)、かなりのパフォーマンスが得られるはずです。

アルゴリズムの概要

簡単に言えば、検索するパターンを指定し、実装はそれを 1 ビット シフトして、シフトされたパターンを記録します。また、シフトされたパターンのマスクも計算します。バイト境界で整列されていないビット パターンの場合、適切な動作のために比較の最初と最後のビットを無視する必要があります。

検索は、一致が見つかるか、データ バッファの最後に到達するまで、各シフト位置のすべてのパターン ビットに対して実行されます。

//
//  BitStringMatch.cpp
//

#include "stdafx.h"
#include <iostream>
#include <cstdint>
#include <vector>
#include <memory>
#include <cassert>

int _tmain(int argc, _TCHAR* argv[])
{
    //Enter text and pattern data as appropriate for your application.  This implementation assumes pattern bits % CHAR_BIT == 0
    uint8_t text[] = { 0xcc, 0xcc, 0xcc, 0x5f, 0xe0, 0x1f, 0xe0, 0x0c }; //1010 1010, 1010 1010, 1010 1010, 010*1 1111, 1110 0000, 0001 1111, 1110 0000, 000*0 1010 
    uint8_t pattern[] = { 0xff, 0x00, 0xff, 0x00 }; //Set pattern to 1111 1111, 0000 0000, 1111 1111, 0000 0000

    assert( CHAR_BIT == 8 ); //Sanity check
    assert ( sizeof( text ) >= sizeof( pattern ) ); //Sanity check

    std::vector< std::vector< uint8_t > > shiftedPatterns( CHAR_BIT, std::vector< uint8_t >( sizeof( pattern ) + 1, 0 ) );  //+1 to accomodate bit shifting of CHAR_BIT bits.
    std::vector< std::pair< uint8_t, uint8_t > > compareMasks( CHAR_BIT, std::pair< uint8_t, uint8_t >( 0xff, 0x00 ) );

    //Initialize pattern shifting through all bit positions
    for( size_t i = 0; i < sizeof( pattern ); ++i ) //Start by initializing the unshifted pattern
    {
        shiftedPatterns[ 0 ][ i ] = pattern[ i ];
    }

    for( size_t i = 1; i < CHAR_BIT; ++i )  //Initialize the other patterns, shifting the previous vector pattern to the right by 1 bit position
    {
        compareMasks[ i ].first >>= i;  //Set the bits to consider in the first...
        compareMasks[ i ].second = 0xff << ( CHAR_BIT - i ); //and last bytes of the pattern

        bool underflow = false;
        for( size_t j = 0; j < sizeof( pattern ) + 1; ++j )
        {
            bool thisUnderflow = shiftedPatterns[ i - 1 ][ j ] & 0x01 ? true : false; 
            shiftedPatterns[ i ][ j ] = shiftedPatterns[ i - 1][ j ] >> 1;

            if( underflow ) //Previous byte shifted out a 1; shift in a 1
            {
                shiftedPatterns[ i ][ j ] |= 0x80;  //Set MSb to 1
            }

            underflow = thisUnderflow;
        }
    }

    //Search text for pattern
    size_t maxTextPos = sizeof( text ) - sizeof( pattern );
    size_t byte = 0;
    bool match = false;
    for( size_t byte = 0; byte <= maxTextPos && !match; ++byte )
    {
        for( size_t bit = 0; bit < CHAR_BIT && ( byte < maxTextPos || ( byte == maxTextPos && bit < 1 ) ); ++bit )
        {
            //Compare first byte of pattern
            if( ( shiftedPatterns[ bit ][ 0 ] & compareMasks[ bit ].first ) != ( text[ byte ] & compareMasks[ bit ].first ) )
            {
                continue;
            }

            size_t foo = sizeof( pattern );
            //Compare all middle bytes of pattern
            bool matchInProgress = true;
            for( size_t pos = 1; pos < sizeof( pattern ) && matchInProgress; ++pos )
            {
                matchInProgress = shiftedPatterns[ bit ][ pos ] == text[ byte + pos ];
            }
            if( !matchInProgress )
            {
                continue;
            }

            if( bit != 0 )  //If compare failed or we're comparing the unshifted pattern, there's no need to compare final pattern buffer byte
            {
                if( ( shiftedPatterns[ bit ][ sizeof( pattern ) ] & compareMasks[ bit ].second ) != ( text[ byte + sizeof( pattern ) ] & compareMasks[ bit ].second ) )
                {
                    continue;
                };
            }

            //We found a match!
            match = true;   //Abandon search
            std::cout << "Match found!  Pattern begins at byte index " << byte << ", bit position " << CHAR_BIT - bit - 1 << ".\n";
            break;
        }
    }
    //If no match
    if( !match )
    {
        std::cout << "No match found.\n";
    }

    std::cout << "\nPress a key to exit...";
    std::getchar();

    return 0;
}

これがお役に立てば幸いです。

于 2012-08-30T09:04:40.617 に答える
3

N が大きい場合 (たとえば、16 ビットより大きい場合)、ビット パターンの 8 つのシフトされたコピーに対して予備検索を行うのは非常に簡単です (パターンを切り捨てて「部分的なバイト」を削除します)。次に、隣接するビットを調べて結果を絞り込むことができます。バイト検索 (8 つのシフトされたコピーに対する) は、Boyer-Moore または同様の効率的なアルゴリズムを使用して実行できます。

ご参考までに: 8 バイト検索は、各バイト比較に 1 つの命令しかかからないため、おそらく 1 ビット検索よりも高速ですが、ビット検索を実行するために必要なビット操作には、ビットごとにはるかに多くの命令が必要です。ただし、確実にプロファイリングする必要があります。

于 2012-08-24T05:14:45.700 に答える
0

パフォーマンスは、ビット パターンの種類に大きく依存します。たとえば、「キー」ビット シーケンスと「検索」ビット シーケンスが大きく異なる場合、一部のバイト シフト ソリューション、またはビットごとのソリューションでさえかなり高速になります。ほとんどのビット オフセットでは、最初の比較が失敗し、次の比較に進むことができるためです。

シーケンスが非常に類似している場合は、より洗練されたアルゴリズムが必要です。たとえば、すべてが 10101010101010 である 100 万ビットを想像してみてください。ただし、真ん中のどこかで 1 がゼロに反転されます (例: ...101000101...)。 ...101000" の場合、50 万回一致に失敗する前に、大量のバイトを 8 回比較する必要があるため、バイト シフト比較アルゴリズムはうまく機能しません。

したがって、ここではデータの統計的特性が重要です。また、キー文字列が複数回使用されている場合、または複数の一致が予想される場合は、バッファを処理することで速度が向上します。

たとえば、バッファを一度変換して、バイトの各ペアと各バイトのビット数を合計し、それを各キー文字列に対して行うことができます。その後、2 つのバッファーをスキャンできます。文字列が一致する可能性がある場合、キー文字列の各バイトのビット数は常に各バイト ペアのビット数より小さくなければならず、キー文字列の各バイト ペアのビット数は常に検索文字列の各バイトのビット数。

キー文字列が大きい場合は、低ビットと高ビットの「アンカー」をマークして、それらをスキャンできます。たとえば、オフセット x、y、z に 2 つの 0 バイトがある 10k のキー文字列を比較していたとします。次に、検索文字列をスキャンして、それらのオフセットでの単一バイトのビット数がゼロに一致する位置を見つけることができます。それは本当に速いでしょう。

于 2012-08-30T19:13:43.710 に答える