programing

비트 스트림에서 비트 패턴을 검색하는 가장 빠른 방법

linuxpc 2023. 8. 29. 20:11
반응형

비트 스트림에서 비트 패턴을 검색하는 가장 빠른 방법

비트 스트림에서 16비트 단어를 스캔해야 합니다.바이트 또는 단어 경계에 정렬할 수는 없습니다.

이를 달성하는 가장 빠른 방법은 무엇입니까?테이블 및/또는 시프트를 사용하는 다양한 브루트 포스 방법이 있습니다. 하지만 yes/no를 지정하여 계산 수를 줄일 수 있는 "비트 트위들링 바로 가기"가 있습니까?/아마도 각 바이트 또는 단어가 도착할 때마다 플래그 결과를 포함할 수 있습니다.

C 코드, 내장형, x86 기계 코드 모두 흥미로울 것입니다.

단순한 폭력을 사용하는 것은 때때로 좋습니다.

이 된 것 를 들어 나는프인단가값이 16어을에넣치어당이신생 16배얻다런다각열이니을었합고다니정합고리동키이콜시의가인uming▁iass▁like▁arrayal▁and▁an▁got▁int▁word▁of다▁this(▁(▁you▁values▁them▁put▁prec▁think정니▁shifted▁the다합▁16생▁all니나s는c각).int 2입니다보다 두 배 더.short)

 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);
 }

그리고 스트림에서 나오는 모든 부호 없는 단락에 대해 해당 부호 없는 단락과 이전 단락의 의미를 만들고 부호 없는 단락을 16개의 부호 없는 단락과 비교합니다.둘 중 하나라도 일치하면 하나를 얻으실 수 있습니다.

기본적으로 다음과 같습니다.

  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;
  }

패턴이 동일한 비트에서 두 번 이상 감지될 경우 잠재적으로 다중 적중을 의미할 수 있습니다.

예를 들어 32비트의 0과 탐지하려는 패턴이 160입니다. 그러면 패턴이 16번 탐지된다는 의미입니다.


이것의 시간 비용은 대략 쓰여진 대로 컴파일된다고 가정하면, 입력 단어당 16개의 체크입니다. 하나의 을▁one다▁per니,를 합니다.&그리고.==분기 또는 기타 조건부 증분.또한 모든 비트에 대한 마스크에 대한 테이블 룩업도 수행합니다.

테이블 조회가 필요하지 않습니다. 대신 오른쪽으로 이동합니다.combinedx86에서 SIMD를 사용하여 이를 벡터화하는 방법을 보여주는 다른 답변에서 볼 수 있듯이, 우리는 훨씬 더 효율적인 asm을 얻습니다.

두 문자 {0, 1}의 알파벳에 대한 Knuth-Morris-Pratt 알고리듬이나 레이니어의 아이디어가 충분히 빠르지 않으면 검색 속도를 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

그런 다음 다음 다음을 사용하여 비트 스트림에서 일치하는 위치를 찾을 수 있습니다.

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

256개 테이블 항목 중 최대 8개가 0이 아니기 때문에 평균적으로 32번째 위치만 자세히 살펴봐야 합니다.이 바이트(이전과 이후의 바이트와 결합)에 대해서만 일치하는 항목이 있는지 확인하려면 레이니어에서 제안한 대로 비트 연산 또는 일부 마스킹 기술을 사용해야 합니다.

코드에서는 리틀 엔디안 바이트 순서를 사용한다고 가정합니다.바이트의 비트 순서도 문제가 될 수 있습니다(CRC32 체크섬을 이미 구현한 모든 사람에게 알려져 있음).

256 사이즈 룩업 테이블 3개를 이용한 해결책을 제안하고자 합니다.이것은 큰 비트 스트림에 효율적입니다.이 솔루션은 비교를 위해 샘플에서 3바이트를 사용합니다.다음 그림은 3바이트 단위의 16비트 데이터의 가능한 모든 배열을 보여줍니다.각 바이트 영역은 서로 다른 색상으로 표시됩니다.

alt 텍스트 http://img70.imageshack.us/img70/8711/80541519.jpg

여기서 1~8번 검사는 첫 번째 샘플에서, 9~16번 검사는 다음 샘플에서 처리됩니다.이제 패턴을 검색할 때 이 패턴의 가능한 8가지 배열(아래와 같이)을 모두 찾고 3개의 룩업 테이블(왼쪽, 중간, 오른쪽)에 저장합니다.

룩업 테이블 초기화 중:

.0111011101110111찾을 수 있는 패턴으로.이제 네 번째 배열을 생각해 보십시오.왼쪽은.XXX01110part(왼쪽 부분을 것)로.XXX01110)와 함께000100001은 입력 패턴의 배열 시작 위치를 나타냅니다.따라서 왼쪽 조회 테이블의 8개 원시는 16개로 채워집니다(00010000).

00001110
00101110
01001110
01101110
10001110
10101110
11001110
11101110

정리의 중간 부분은.11101110이입니다.00010000).

이제 준비의 오른쪽 부분은111XXXXX가 인스가있모원시(32개원)인 날111XXXXX채워질 입니다.00010000).

채우는 동안 조회 테이블의 요소를 덮어쓰지 않아야 합니다.대신 비트 OR 연산을 수행하여 이미 채워진 원시를 업데이트합니다.위의 예에서 3차 배열로 작성된 모든 원시는 7차 배열로 다음과 같이 업데이트됩니다.

alt text

가 따서인있는인 입니다.XX011101 lookup 및 왼쪽조테서에에서11101110 및 중간 111XXXXX이 오쪽조테다이업로데됩트니다이으음이로 됩니다.00100010일곱 번째 협정으로

패턴 검색 중:

3바이트의 샘플을 채취합니다.왼쪽은 왼쪽 룩업 테이블, 중간은 중간 룩업 테이블, 오른쪽은 오른쪽 룩업 테이블에서 카운트를 찾습니다.

Count = Left[Byte0] & Middle[Byte1] & Right[Byte2];

카운트에서 숫자 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;
    }

패턴 검색 중:

데이터는 스트림 버퍼, 왼쪽은 룩업 테이블, 중간은 룩업 테이블, 오른쪽은 룩업 테이블입니다.

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 );
    }
}

제한 사항:

루프는 스트림 버퍼의 맨 끝에 배치된 패턴을 감지할 수 없습니다.이 제한을 극복하려면 다음 코드를 루프 후에 추가해야 합니다.

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

if( Count )
{
    TotalCount += GetNumberOfOnes( Count );
}

장점:

이 알고리즘은 N바이트 배열에서 패턴을 찾는 데 N-1 논리적 단계만 사용합니다.오버헤드만 처음에는 모든 경우에 일정한 룩업 테이블을 채우는 것입니다.따라서 대용량 바이트 스트림을 검색하는 데 매우 효과적입니다.

내 돈은 크누스-모리-프랫에 있고 알파벳은 두 글자입니다.

저는 16개의 상태를 가진 상태 기계를 구현할 것입니다.

각 상태는 패턴을 준수하는 수신 비트 수를 나타냅니다.수신된 다음 비트가 패턴의 다음 비트와 일치하면 기계는 다음 상태로 이동합니다.그렇지 않은 경우, 기계는 첫 번째 상태(또는 패턴의 시작이 더 적은 수의 수신 비트와 일치할 수 있는 경우 다른 상태)로 돌아갑니다.

기계가 마지막 상태에 도달하면 비트 스트림에서 패턴이 식별되었음을 나타냅니다.

아톰스

루크와 Msalter의 세부 사항에 대한 더 많은 정보 요청을 고려하기 전까지는 좋아 보였습니다.

세부 사항은 KMP보다 더 빠른 접근을 나타낼 수 있습니다. KMP 기사는 다음과 같이 연결됩니다.

검색 패턴이 'AAAAAAA'인 특정한 경우.다중 패턴 검색의 경우

아마도 가장 적합할 것입니다.

소개에 대한 자세한 내용은 여기에서 확인할 수 있습니다.

SIMD 지침을 잘 사용하는 것 같습니다.SSE2는 여러 정수를 동시에 크런치하기 위한 정수 명령을 여러 개 추가했지만, 데이터가 정렬되지 않기 때문에 비트 이동이 많지 않은 솔루션은 상상할 수 없습니다.이것은 실제로 FPGA가 해야 할 일처럼 들립니다.

제가 할 일은 16개의 접두사와 16개의 접미사를 만드는 것입니다.그런 다음 각 16비트 입력 청크에 대해 가장 긴 접미사 일치 여부를 결정합니다.다음 청크에 길이가 일치하는 접두사가 있으면 일치하는 항목이 있습니다.(16-N)

접미사 일치는 실제로 16가지 비교가 아닙니다.그러나 패턴 단어를 기반으로 사전 계산이 필요합니다.예를 들어 패턴 워드가 10101010101010101010이면 먼저 16비트 입력 청크의 마지막 비트를 테스트할 수 있습니다.이 비트가 0이면 ...101010으로 충분한지 테스트만 하면 됩니다.마지막 비트가 1이면 ...1010101로 충분한지 테스트해야 합니다.각각 8개씩 총 1+8개의 비교 결과를 얻을 수 있습니다.패턴 워드가 11111111110000인 경우에도 입력의 마지막 비트에서 접미사 일치 여부를 테스트합니다.해당 비트가 1이면 12개의 접미사 일치(regex: 1{1,12})를 수행해야 하지만 0이면 평균 9개의 테스트에 대해 4개의 일치(regex 11111111110{1,4})만 가능합니다.추가합니다.16-N접두사가 일치하면 16비트 청크당 10개의 체크만 필요하다는 것을 알 수 있습니다.

범용 비 SIMD 알고리즘의 경우 다음과 같은 작업보다 훨씬 더 잘 수행할 수 없을 것입니다.

unsigned int const pattern = pattern to search for
unsigned int accumulator = first three input bytes

do
{
  bool const found = ( ((accumulator   ) & ((1<<16)-1)) == pattern )
                   | ( ((accumulator>>1) & ((1<<16)-1)) == pattern );
                   | ( ((accumulator>>2) & ((1<<16)-1)) == pattern );
                   | ( ((accumulator>>3) & ((1<<16)-1)) == pattern );
                   | ( ((accumulator>>4) & ((1<<16)-1)) == pattern );
                   | ( ((accumulator>>5) & ((1<<16)-1)) == pattern );
                   | ( ((accumulator>>6) & ((1<<16)-1)) == pattern );
                   | ( ((accumulator>>7) & ((1<<16)-1)) == pattern );
  if( found ) { /* pattern found */ }
  accumulator >>= 8;

  unsigned int const data = next input byte
  accumulator |= (data<<8);
} while( there is input data left );

매우 큰 입력(n 값)에 대해 고속 푸리에 변환을 사용하여 O(n log n ) 시간 내에 모든 비트 패턴을 찾을 수 있습니다.비트 마스크와 입력의 상호 상관 관계를 계산합니다.각각 크기 n과 n'을 갖는 시퀀스 x와 마스크 y의 교차 상관은 다음과 같이 정의됩니다.

R(m) = sum  _ k = 0 ^ n' x_{k+m} y_k

그런 다음 R(m) = Y(Y)에서 비트 마스크에 포함된 값의 합인 경우 마스크와 정확히 일치하는 비트 패턴이 발생합니다.

따라서 비트 패턴을 일치시키려면

[0 0 1 0 1 0]

[ 1 1 0 0 1 0 1 0 0 0 1 0 1 0 1]

그러면 마스크를 사용해야 합니다.

[-1 -1  1 -1  1 -1]

마스크에 있는 -1은 그 장소들이 0이어야 한다는 것을 보장합니다.

O(n log n ) 시간의 FFT를 사용하여 상호 상관 관계를 구현할 수 있습니다.

저는 KMP가 O(n + k) 런타임을 가지고 있기 때문에 이것을 능가한다고 생각합니다.

모든 비트 위치를 확인하는 @Toad의 간단한 브루트 포스 알고리즘을 구현하는 더 간단한 방법은 마스크를 이동하는 대신 데이터를 제자리로 이동하는 것입니다.어레이가 필요하지 않으며, 오른쪽으로 이동하는 것이 훨씬 간편합니다.combined >>= 1 (마스크를 하거나 16비트로 합니다.)uint16_t.)

여러 문제에서 마스크를 만드는 것이 원하지 않는 비트를 이동하는 것보다 효율성이 떨어지는 경향이 있다는 것을 알게 되었습니다.

((의) 의 가장 하는 데 실패했습니다.uint16_t또는 특히 홀수 크기 바이트 배열의 마지막 바이트는 독자를 위한 연습으로 남겨집니다.)

// simple brute-force scalar version, checks every bit position 1 at a time.
long bitstream_search_rshift(uint8_t *buf, size_t len, unsigned short pattern)
{
        uint16_t *bufshort = (uint16_t*)buf;  // maybe unsafe type punning
        len /= 2;
        for (size_t i = 0 ; i<len-1 ; i++) {
                //unsigned short curWord = bufshort[i];
                //unsigned short prevWord = bufshort[i+1];
                //int combinedWords = (prevWord<<16) + curWord;

                uint32_t combined;                                // assumes little-endian
                memcpy(&combined, bufshort+i, sizeof(combined));  // safe unaligned load

                for(int bitpos=0; bitpos<16; bitpos++) {
                        if( (combined&0xFFFF) == pattern)     // compiles more efficiently on e.g. old ARM32 without UBFX than (uint16_t)combined
                                return i*16 + bitpos;
                        combined >>= 1;
                }
        }
        return -1;
}

이것은 x86, AArch64 및 ARM과 같은 대부분의 ISA에 대해 최근 gcc 및 clang이 있는 어레이에서 마스크를 로드하는 것보다 훨씬 더 효율적으로 컴파일합니다.

풀어서 추출 를 즉시 피연산자와 함께 사용할 수 예: ARM 컴개러의 16와루완프풀 (ARM 같파은과자일를히전다어산연있피수사니습).ubfx부호 없는 비트 필드 추출기 전원PC 회전-왼쪽 + 즉시 마스크 비트 범위)를 사용하여 정기적으로 비교 및 분기를 수행할 수 있는 32비트 또는 64비트 레지스터의 하단에 16비트를 추출합니다.실제로 오른쪽 이동의 종속성 사슬은 1개도 없습니다.

비트를 할 수 를 들어 x86은 CPU이고 16비트입니다.cmp cx,dxcombinededx

이만큼 잘 처리합니다를 들어, clang은 일 부용 @Toad 만을전잘다처큼전을 사용하여 합니다. 예를 들어, PowerPC용 clang은 마스크 배열을 최적화합니다.rlwinm의 16비트 범위를 마스킹합니다.combined즉시를 사용하여 16개의 레지스터에 16개의 사전 패턴 값을 모두 유지하므로, 어느 쪽이든 rlwinm이 0이 아닌 회전 카운트를 가지는지 여부는 rlwinm / compare / branch입니다.하지만 오른쪽 시프트 버전은 16 tmp 레지스터를 설정할 필요가 없습니다.https://godbolt.org/z/8mUaDI


AVX2 브루트 포스

이 작업에는 (최소) 두 가지 방법이 있습니다.

  • 다음으로 넘어가기 전에 단일 dword를 브로드캐스트하고 변수 이동을 사용하여 해당 dword의 모든 비트 전송을 확인합니다.잠재적으로 일치하는 위치를 찾기가 매우 쉽습니다. (모든 일치 항목을 세고 싶다면 덜 좋을 수도 있습니다.)
  • 벡터 로드, 병렬로 여러 데이터 창의 비트 전송을 반복합니다.인접 워드(16비트)에서 시작하는 정렬되지 않은 로드를 사용하여 홀수/짝수 벡터를 중첩하여 dword(32비트) 창을 얻을 수 있습니다.그렇지 않으면 128비트 레인을 통과해야 하며, 가급적이면 16비트 세분화를 사용해야 하며, AVX512 없이 2개의 명령이 필요합니다.

32비트 대신 64비트 요소 시프트를 사용하면 항상 상위 16개(제로가 시프트됨)를 무시하는 대신 인접한 여러 16비트 창을 확인할 수 있습니다.그러나 더 높은 주소의 실제 데이터 대신 0이 이동되는 SIMD 요소 경계에서 휴식 시간이 있습니다. (향후 솔루션:AVX512VB의 SIMD 버전과 같은 MI2 이중 시프트SHRD.)

어쨌든 이렇게 할 가치가 있을 수 있습니다. 그리고 나서 각 64비트 요소의 맨 위에 놓쳤던 4x16비트 요소를 다시 찾아보십시오.__m256i여러 벡터에 걸쳐 나머지를 결합하는 것일 수 있습니다.

// simple brute force, broadcast 32 bits and then search for a 16-bit match at bit offset 0..15

#ifdef __AVX2__
#include <immintrin.h>
long bitstream_search_avx2(uint8_t *buf, size_t len, unsigned short pattern)
{
    __m256i vpat = _mm256_set1_epi32(pattern);

    len /= 2;
    uint16_t *bufshort = (uint16_t*)buf;
    for (size_t i = 0 ; i<len-1 ; i++) {
        uint32_t combined; // assumes little-endian
        memcpy(&combined, bufshort+i, sizeof(combined));  // safe unaligned load
        __m256i v = _mm256_set1_epi32(combined);
//      __m256i vlo = _mm256_srlv_epi32(v, _mm256_set_epi32(7,6,5,4,3,2,1,0));
//      __m256i vhi = _mm256_srli_epi32(vlo, 8);

        // shift counts set up to match lane ordering for vpacksswb

        // SRLVD cost: Skylake: as fast as other shifts: 1 uop, 2-per-clock
        // * Haswell: 3 uops
        // * Ryzen: 1 uop, but 3c latency and 2c throughput.  Or 4c / 4c for ymm 2 uop version
        // * Excavator: latency worse than PSRLD xmm, imm8 by 1c, same throughput. XMM: 3c latency / 1c tput.  YMM: 3c latency / 2c tput.  (http://users.atw.hu/instlatx64/AuthenticAMD0660F51_K15_BristolRidge_InstLatX64.txt)  Agner's numbers are different.
        __m256i vlo = _mm256_srlv_epi32(v, _mm256_set_epi32(11,10,9,8,    3,2,1,0));
        __m256i vhi = _mm256_srlv_epi32(v, _mm256_set_epi32(15,14,13,12,  7,6,5,4));

        __m256i cmplo = _mm256_cmpeq_epi16(vlo, vpat);  // low 16 of every 32-bit element = useful
        __m256i cmphi = _mm256_cmpeq_epi16(vhi, vpat);

        __m256i cmp_packed = _mm256_packs_epi16(cmplo, cmphi); // 8-bit elements, preserves sign bit
        unsigned cmpmask = _mm256_movemask_epi8(cmp_packed);
        cmpmask &= 0x55555555;  // discard odd bits
        if (cmpmask) {
            return i*16 + __builtin_ctz(cmpmask)/2;
        }
    }
    return -1;
}
#endif

이것은 특히 처음 32바이트 미만의 데이터에서 일반적으로 히트를 빨리 찾는 검색에 유용합니다.이것은 대규모 검색(그러나 여전히 순수한 무차별적인 힘이며, 한 번에 한 단어만 확인)에 나쁘지 않으며, 스카이레이크에서는 여러 창의 16개 오프셋을 동시에 확인하는 것보다 나쁘지 않을 수 있습니다.

이는 가변 시프트의 효율성이 낮은 다른 CPU의 경우 Skylake에 맞게 조정되었으며 오프셋 0.7에 대해 변수 시프트 1개만 고려한 다음 오프셋 8을 생성할 수 있습니다.그것을 옮김으로써 15.아니면 완전히 다른 것.

Godbolt의 gcc/clang(Godbolt의 경우)과 메모리에서 바로 브로드캐스트되는 내부 루프와 놀라울 정도로 잘 컴파일됩니다. (최적화)memcpy되지 않은 및 정되지 부하및은렬set1()vpbroadcastd)

Godbolt가 포함되어 있습니다.main이것은 작은 어레이에서 실행됩니다. (지난 번 수정 이후로 테스트하지 않았을 수도 있지만, 이전에 테스트했고 패킹 + 비트 스캔 기능이 작동합니다.)

## clang8.0  -O3 -march=skylake  inner loop
.LBB0_2:                                # =>This Inner Loop Header: Depth=1
        vpbroadcastd    ymm3, dword ptr [rdi + 2*rdx]   # broadcast load
        vpsrlvd ymm4, ymm3, ymm1
        vpsrlvd ymm3, ymm3, ymm2             # shift 2 ways
        vpcmpeqw        ymm4, ymm4, ymm0
        vpcmpeqw        ymm3, ymm3, ymm0     # compare those results
        vpacksswb       ymm3, ymm4, ymm3     # pack to 8-bit elements
        vpmovmskb       ecx, ymm3            # scalar bitmask
        and     ecx, 1431655765              # see if any even elements matched
        jne     .LBB0_4            # break out of the loop on found, going to a tzcnt / ... epilogue
        add     rdx, 1
        add     r8, 16           # stupid compiler, calculate this with a multiply on a hit.
        cmp     rdx, rsi
        jb      .LBB0_2                    # } while(i<len-1);
        # fall through to not-found.

이는 8 웁스의 작업 + 3 웁스의 루프 오버헤드입니다(및/jne의 매크로 융합 및 Haswell/Skylake에서 사용할 cmp/jb의 매크로 융합을 가정합니다).256비트 명령어가 여러 웁스인 AMD에서는 더 많을 것입니다.


물론 모든 요소를 1씩 이동하고 동일한 창에서 여러 오프셋 대신 여러 창을 병렬로 확인하려면 일반 오른쪽 시프트를 사용해야 합니다.

효율적인 변수 이동(특히 AVX2가 전혀 없음)이 없으면 히트가 발생할 경우를 대비하여 첫 번째 히트가 있는 위치를 정리하는 작업이 조금 더 필요하더라도 큰 검색에 더 적합합니다. (가장 낮은 요소가 아닌 다른 곳에서 히트를 찾은 후에는 모든 이전 윈도우의 나머지 오프셋을 확인해야 합니다.)

아마도 당신은 당신의 비트 스트림을 벡터(vec_str)로 스트리밍하고, 당신의 패턴을 다른 벡터(vec_pattern)로 스트리밍한 다음 아래 알고리즘과 같은 것을 해야 할 것입니다.

i=0
while i<vec_pattern.length
    j=0
    while j<vec_str.length
            if (vec_str[j] xor vec_pattern[i])
                i=0
                j++

(알고리즘이 정확하기를 바랍니다)

빅 비트 문자열에서 일치하는 항목을 찾는 가장 빠른 방법은 주어진 입력 바이트가 패턴과 일치하는 비트 오프셋을 보여주는 조회 테이블을 계산하는 것입니다.그런 다음 세 개의 연속적인 오프셋 일치를 함께 결합하면 전체 패턴과 일치하는 오프셋을 보여주는 비트 벡터를 얻을 수 있습니다.예를 들어 바이트 x가 패턴의 처음 3비트와 일치하고 바이트 x+1이 비트 3.11과 일치하고 바이트 x+2가 비트 11.16과 일치하는 경우 바이트 x+5비트가 일치합니다.

다음은 한 번에 두 바이트에 대한 결과를 누적하는 몇 가지 코드 예제입니다.

void find_matches(unsigned char* sequence, int n_sequence, unsigned short pattern) {
    if (n_sequence < 2)
        return; // 0 and 1 byte bitstring can't match a short

    // Calculate a lookup table that shows for each byte at what bit offsets
    // the pattern could match.
    unsigned int match_offsets[256];
    for (unsigned int in_byte = 0; in_byte < 256; in_byte++) {
        match_offsets[in_byte] = 0xFF;
        for (int bit = 0; bit < 24; bit++) {
            match_offsets[in_byte] <<= 1;
            unsigned int mask = (0xFF0000 >> bit) & 0xFFFF;
            unsigned int match_location = (in_byte << 16) >> bit;
            match_offsets[in_byte] |= !((match_location ^ pattern) & mask);
        }
    }

    // Go through the input 2 bytes at a time, looking up where they match and
    // anding together the matches offsetted by one byte. Each bit offset then
    // shows if the input sequence is consistent with the pattern matching at
    // that position. This is anded together with the large offsets of the next
    // result to get a single match over 3 bytes.
    unsigned int curr, next;
    curr = 0;
    for (int pos = 0; pos < n_sequence-1; pos+=2) {
        next = ((match_offsets[sequence[pos]] << 8) | 0xFF) & match_offsets[sequence[pos+1]];
        unsigned short match = curr & (next >> 16);
        if (match)
            output_match(pos, match);
        curr = next;
    }
    // Handle the possible odd byte at the end
    if (n_sequence & 1) {
        next = (match_offsets[sequence[n_sequence-1]] << 8) | 0xFF;
        unsigned short match = curr & (next >> 16);
        if (match)
            output_match(n_sequence-1, match);
    }
}

void output_match(int pos, unsigned short match) {
    for (int bit = 15; bit >= 0; bit--) {
        if (match & 1) {
            printf("Bitstring match at byte %d bit %d\n", (pos-2) + bit/8, bit % 8);
        }
        match >>= 1;
    }
}

이것의 주 루프는 18개의 명령어 길이이며 반복당 2바이트를 처리합니다.설치 비용이 문제가 되지 않는다면 이 작업은 최대한 빨리 진행될 것입니다.

언급URL : https://stackoverflow.com/questions/1572290/fastest-way-to-scan-for-bit-pattern-in-a-stream-of-bits

반응형