C에서 비트 반전을위한 효율적인 알고리즘 (MSB-> LSB에서 LSB-> MSB로)


243

다음을 달성하는 가장 효율적인 알고리즘은 무엇입니까?

0010 0000 => 0000 0100

MSB-> LSB에서 LSB-> MSB로 변환됩니다. 모든 비트는 반대로해야합니다. 즉, 이것은 엔디안 스왑 이 아닙니다 .


1
적절한 이름은 비트 단위 연산이라고 생각합니다.
Kredns 2009

5
나는 당신이 회전이 아니라 반전을 의미한다고 생각합니다.
Juliano

2
대부분의 ARM 프로세서에는 기본 제공 작업이 있습니다. ARM Cortex-M0은 그렇지 않으며 바이트 단위 테이블을 사용하여 비트를 교환하는 것이 가장 빠른 방법이라는 것을 알았습니다.
starblue

2
Sean Eron Anderson의 Bit Twiddling Hacks 도 참조하십시오 .
jww

2
"best"를 정의하십시오
Lee Taylor

답변:


497

노트 : 아래의 모든 알고리즘은 C 언어이지만 원하는 언어로 이식 가능해야합니다 (빠르지 않을 때 나를 보지 마십시오).

옵션

메모리 부족 (32 비트 int, 32 비트 시스템) ( 여기에서 ) :

unsigned int
reverse(register unsigned int x)
{
    x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1));
    x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2));
    x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4));
    x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8));
    return((x >> 16) | (x << 16));

}

유명한 Bit Twiddling Hacks 페이지에서 :

가장 빠른 (조회 테이블) :

static const unsigned char BitReverseTable256[] = 
{
  0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 
  0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 
  0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, 
  0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, 
  0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, 
  0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
  0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, 
  0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
  0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
  0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, 
  0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
  0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
  0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, 
  0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
  0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, 
  0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
};

unsigned int v; // reverse 32-bit value, 8 bits at time
unsigned int c; // c will get v reversed

// Option 1:
c = (BitReverseTable256[v & 0xff] << 24) | 
    (BitReverseTable256[(v >> 8) & 0xff] << 16) | 
    (BitReverseTable256[(v >> 16) & 0xff] << 8) |
    (BitReverseTable256[(v >> 24) & 0xff]);

// Option 2:
unsigned char * p = (unsigned char *) &v;
unsigned char * q = (unsigned char *) &c;
q[3] = BitReverseTable256[p[0]]; 
q[2] = BitReverseTable256[p[1]]; 
q[1] = BitReverseTable256[p[2]]; 
q[0] = BitReverseTable256[p[3]];

이 아이디어를 64 비트로 확장 int하거나 속도를 위해 메모리를 교환 (L1 데이터 캐시가 충분히 크다고 가정)하고 64K 항목 조회 테이블을 사용하여 한 번에 16 비트를 되돌릴 수 있습니다.


기타

단순한

unsigned int v;     // input bits to be reversed
unsigned int r = v & 1; // r will be reversed bits of v; first get LSB of v
int s = sizeof(v) * CHAR_BIT - 1; // extra shift needed at end

for (v >>= 1; v; v >>= 1)
{   
  r <<= 1;
  r |= v & 1;
  s--;
}
r <<= s; // shift when v's highest bits are zero

더 빠른 (32 비트 프로세서)

unsigned char b = x;
b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16; 

더 빠른 (64 비트 프로세서)

unsigned char b; // reverse this (8-bit) byte
b = (b * 0x0202020202ULL & 0x010884422010ULL) % 1023;

32 비트 에서이 작업을 수행 int하려면 각 바이트의 비트를 뒤집고 바이트 순서를 반대로하십시오. 그건:

unsigned int toReverse;
unsigned int reversed;
unsigned char inByte0 = (toReverse & 0xFF);
unsigned char inByte1 = (toReverse & 0xFF00) >> 8;
unsigned char inByte2 = (toReverse & 0xFF0000) >> 16;
unsigned char inByte3 = (toReverse & 0xFF000000) >> 24;
reversed = (reverseBits(inByte0) << 24) | (reverseBits(inByte1) << 16) | (reverseBits(inByte2) << 8) | (reverseBits(inByte3);

결과

가장 유망한 두 가지 솔루션 인 조회 테이블과 비트 단위 AND (첫 번째)를 벤치마킹했습니다. 테스트 머신은 4GB DDR2-800이 장착 된 랩탑 및 2.4GHz, Core 2 Duo T7500, 4MB L2 캐시입니다. YMMV. 64 비트 Linux에서 gcc 4.3.2를 사용했습니다 . OpenMP (및 GCC 바인딩)는 고해상도 타이머에 사용되었습니다.

reverse.c

#include <stdlib.h>
#include <stdio.h>
#include <omp.h>

unsigned int
reverse(register unsigned int x)
{
    x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1));
    x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2));
    x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4));
    x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8));
    return((x >> 16) | (x << 16));

}

int main()
{
    unsigned int *ints = malloc(100000000*sizeof(unsigned int));
    unsigned int *ints2 = malloc(100000000*sizeof(unsigned int));
    for(unsigned int i = 0; i < 100000000; i++)
      ints[i] = rand();

    unsigned int *inptr = ints;
    unsigned int *outptr = ints2;
    unsigned int *endptr = ints + 100000000;
    // Starting the time measurement
    double start = omp_get_wtime();
    // Computations to be measured
    while(inptr != endptr)
    {
      (*outptr) = reverse(*inptr);
      inptr++;
      outptr++;
    }
    // Measuring the elapsed time
    double end = omp_get_wtime();
    // Time calculation (in seconds)
    printf("Time: %f seconds\n", end-start);

    free(ints);
    free(ints2);

    return 0;
}

reverse_lookup.c

#include <stdlib.h>
#include <stdio.h>
#include <omp.h>

static const unsigned char BitReverseTable256[] = 
{
  0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 
  0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 
  0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, 
  0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, 
  0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, 
  0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
  0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, 
  0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
  0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
  0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, 
  0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
  0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
  0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, 
  0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
  0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, 
  0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
};

int main()
{
    unsigned int *ints = malloc(100000000*sizeof(unsigned int));
    unsigned int *ints2 = malloc(100000000*sizeof(unsigned int));
    for(unsigned int i = 0; i < 100000000; i++)
      ints[i] = rand();

    unsigned int *inptr = ints;
    unsigned int *outptr = ints2;
    unsigned int *endptr = ints + 100000000;
    // Starting the time measurement
    double start = omp_get_wtime();
    // Computations to be measured
    while(inptr != endptr)
    {
    unsigned int in = *inptr;  

    // Option 1:
    //*outptr = (BitReverseTable256[in & 0xff] << 24) | 
    //    (BitReverseTable256[(in >> 8) & 0xff] << 16) | 
    //    (BitReverseTable256[(in >> 16) & 0xff] << 8) |
    //    (BitReverseTable256[(in >> 24) & 0xff]);

    // Option 2:
    unsigned char * p = (unsigned char *) &(*inptr);
    unsigned char * q = (unsigned char *) &(*outptr);
    q[3] = BitReverseTable256[p[0]]; 
    q[2] = BitReverseTable256[p[1]]; 
    q[1] = BitReverseTable256[p[2]]; 
    q[0] = BitReverseTable256[p[3]];

      inptr++;
      outptr++;
    }
    // Measuring the elapsed time
    double end = omp_get_wtime();
    // Time calculation (in seconds)
    printf("Time: %f seconds\n", end-start);

    free(ints);
    free(ints2);

    return 0;
}

나는 여러 가지 최적화에서 두 가지 접근 방식을 시도하고 각 수준에서 3 번의 시험을 실행했으며 각 시험은 1 억 개의 무작위를 거꾸로했습니다 unsigned ints. 조회 테이블 옵션의 경우 비트 해킹 페이지에 제공된 두 가지 구성표 (옵션 1 및 2)를 모두 시도했습니다. 결과는 아래와 같습니다.

비트 AND

mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -o reverse reverse.c
mrj10@mjlap:~/code$ ./reverse
Time: 2.000593 seconds
mrj10@mjlap:~/code$ ./reverse
Time: 1.938893 seconds
mrj10@mjlap:~/code$ ./reverse
Time: 1.936365 seconds
mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -O2 -o reverse reverse.c
mrj10@mjlap:~/code$ ./reverse
Time: 0.942709 seconds
mrj10@mjlap:~/code$ ./reverse
Time: 0.991104 seconds
mrj10@mjlap:~/code$ ./reverse
Time: 0.947203 seconds
mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -O3 -o reverse reverse.c
mrj10@mjlap:~/code$ ./reverse
Time: 0.922639 seconds
mrj10@mjlap:~/code$ ./reverse
Time: 0.892372 seconds
mrj10@mjlap:~/code$ ./reverse
Time: 0.891688 seconds

조회 테이블 (옵션 1)

mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -o reverse_lookup reverse_lookup.c
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.201127 seconds              
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.196129 seconds              
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.235972 seconds              
mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -O2 -o reverse_lookup reverse_lookup.c
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 0.633042 seconds              
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 0.655880 seconds              
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 0.633390 seconds              
mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -O3 -o reverse_lookup reverse_lookup.c
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 0.652322 seconds              
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 0.631739 seconds              
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 0.652431 seconds  

조회 테이블 (옵션 2)

mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -o reverse_lookup reverse_lookup.c
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.671537 seconds
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.688173 seconds
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.664662 seconds
mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -O2 -o reverse_lookup reverse_lookup.c
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.049851 seconds
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.048403 seconds
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.085086 seconds
mrj10@mjlap:~/code$ gcc -fopenmp -std=c99 -O3 -o reverse_lookup reverse_lookup.c
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.082223 seconds
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.053431 seconds
mrj10@mjlap:~/code$ ./reverse_lookup
Time: 1.081224 seconds

결론

옵션 1과 함께 찾아보기 테이블을 사용하십시오.성능이 염려 될 경우 (바이트 주소 지정이 예상보다 느림) . 시스템에서 모든 마지막 바이트의 메모리를 짜야하는 경우 (그리고 비트 반전 성능에 관심이있는 경우) 비트 AND 접근 방식의 최적화 된 버전도 너무 초라하지 않습니다.

경고

예, 벤치 마크 코드가 완전한 해킹이라는 것을 알고 있습니다. 그것을 개선하는 방법에 대한 제안은 환영 이상입니다. 내가 아는 것 :

  • ICC에 액세스 할 수 없습니다. 이것은 더 빠를 수 있습니다 (테스트 할 수 있다면 의견에 응답하십시오).
  • 64K 룩업 테이블은 L1D가 큰 일부 최신 마이크로 아키텍처에서 잘 작동 할 수 있습니다.
  • -mtune = native가 -O2 / -O3에 대해 작동하지 않았습니다 (ld 기호 재정의 오류와 함께 폭발했습니다) 생성 된 코드가 내 마이크로 아키텍처에 맞게 조정되었다고 생각하지 않습니다.
  • SSE를 사용하면이 작업을 약간 더 빠르게 수행 할 수 있습니다. 어떻게해야할지 모르겠지만 빠른 복제, 비트 단위 AND 및 복잡한 지침으로 인해 무언가가 있어야합니다.
  • 위험 할 정도로 충분한 x86 어셈블리 만 알고 있습니다. 다음은 옵션 1에 대해 -O3에 생성 된 코드 GCC입니다. 따라서 본인보다 더 잘 아는 사람이 확인할 수 있습니다.

32 비트

.L3:
movl    (%r12,%rsi), %ecx
movzbl  %cl, %eax
movzbl  BitReverseTable256(%rax), %edx
movl    %ecx, %eax
shrl    $24, %eax
mov     %eax, %eax
movzbl  BitReverseTable256(%rax), %eax
sall    $24, %edx
orl     %eax, %edx
movzbl  %ch, %eax
shrl    $16, %ecx
movzbl  BitReverseTable256(%rax), %eax
movzbl  %cl, %ecx
sall    $16, %eax
orl     %eax, %edx
movzbl  BitReverseTable256(%rcx), %eax
sall    $8, %eax
orl     %eax, %edx
movl    %edx, (%r13,%rsi)
addq    $4, %rsi
cmpq    $400000000, %rsi
jne     .L3

편집 : 나는 또한 uint64_t성능 향상이 있는지 확인하기 위해 컴퓨터에서 유형을 사용하려고했습니다 . 성능은 32 비트보다 약 10 % 빠르며 64 비트 유형을 사용하여 한 번에 두 개의 32 비트 int유형에서 비트를 되돌 리거나 64 비트의 절반을 실제로 반전했는지 여부 와 거의 동일합니다 . 비트 값. 어셈블리 코드는 다음과 같습니다 (이전의 경우 한 번에 두 개의 32 비트 int유형에 대한 반전 비트 ).

.L3:
movq    (%r12,%rsi), %rdx
movq    %rdx, %rax
shrq    $24, %rax
andl    $255, %eax
movzbl  BitReverseTable256(%rax), %ecx
movzbq  %dl,%rax
movzbl  BitReverseTable256(%rax), %eax
salq    $24, %rax
orq     %rax, %rcx
movq    %rdx, %rax
shrq    $56, %rax
movzbl  BitReverseTable256(%rax), %eax
salq    $32, %rax
orq     %rax, %rcx
movzbl  %dh, %eax
shrq    $16, %rdx
movzbl  BitReverseTable256(%rax), %eax
salq    $16, %rax
orq     %rax, %rcx
movzbq  %dl,%rax
shrq    $16, %rdx
movzbl  BitReverseTable256(%rax), %eax
salq    $8, %rax
orq     %rax, %rcx
movzbq  %dl,%rax
shrq    $8, %rdx
movzbl  BitReverseTable256(%rax), %eax
salq    $56, %rax
orq     %rax, %rcx
movzbq  %dl,%rax
shrq    $8, %rdx
movzbl  BitReverseTable256(%rax), %eax
andl    $255, %edx
salq    $48, %rax
orq     %rax, %rcx
movzbl  BitReverseTable256(%rdx), %eax
salq    $40, %rax
orq     %rax, %rcx
movq    %rcx, (%r13,%rsi)
addq    $8, %rsi
cmpq    $400000000, %rsi
jne     .L3

2
지나치게 자세하고 철저한 게시 -1. j / k. +1.
mpen

8
그것이 전부는 아니지만 흥미로운 운동이었습니다. 다른 것이 없다면, 그 과정이 더 가치있는 것을 벤치마킹하고 싶은 다른 누군가에게 건설적인 모습을 보길 바랍니다. :)
Matt J

5
내 ... 신! 내가 찾은 것 같아요 ... 아주 잘 될 수있는 것은 ... 진정한 진실입니다. 문서를 검토하고 추가 조사를 수행해야하지만 스택 오버플로가 아직까지 가장 강력하고 철저하고 유용한 답변이라는 것을 나에게 알려줍니다 (하나님, 저를 도와주세요). John Skeet조차도 놀라고 감동을 받았습니다!
zeboidlund

3
마이크로 벤치마킹의 특정 결함 중 하나 (다른 많은 목록 중에서도)는 룩업 테이블 기반 솔루션을 인위적으로 선호하는 경향이 있다는 점을 명심하십시오. 벤치 마크가 루프에서 한 번의 작업을 반복하기 때문에 캐시 압력이 전혀 없기 때문에 모든 것이 L1에 충돌하기 때문에 L1에 맞는 조회 테이블을 사용하는 것이 가장 빠릅니다. 실제 사용 사례에서는 일반적으로 작업에 캐시 압력이 발생하는 다른 작업이 인터리브됩니다. RAM 누락은 평소보다 10 배 또는 100 배 더 오래 걸릴 수 있지만 벤치 마크에서는 무시됩니다.
BeeOnRope

2
두 솔루션이 서로 가까워지면 LUT의 실제 영향이 심각 할 수 있기 때문에 비 LUT 솔루션 (또는 LUT가 더 작은 솔루션)을 종종 선택합니다. 더 나은 방법은 각 솔루션을 "in situ"벤치마킹하는 것입니다. 실제 솔루션은 실제 입력과 함께 더 큰 응용 프로그램에서 실제로 사용됩니다. 물론 우리는 항상 그럴 시간이 없으며 현실적인 입력이 무엇인지 항상 알지 못합니다.
BeeOnRope

80

이 스레드는 최신 CPU에서도 많은 작업 (CPU 사이클)이 필요한 간단한 문제를 다루기 때문에 관심을 끌었습니다. 그리고 어느 날 나는 동일한 ¤ # % "#"문제로 거기에 서있었습니다. 나는 수백만 바이트를 뒤집어 야했다. 그러나 모든 대상 시스템이 최신 Intel 기반 시스템이라는 것을 알고 있으므로 극단적으로 최적화를 시작하겠습니다 !!!

그래서 Matt J의 조회 코드를 기본으로 사용했습니다. 내가 벤치마킹하는 시스템은 i7 haswell 4700eq입니다.

Matt J의 조회 비트 수 4 억 000 바이트 : 약 0.272 초.

그런 다음 인텔의 ISPC 컴파일러가 reverse.c의 산술을 벡터화 할 수 있는지 확인하려고했습니다.

컴파일러가 물건을 찾는 데 도움을주기 위해 많은 노력을 기울 였기 때문에 여기서 찾은 결과를 보지 않을 것입니다. 크게 줄었지만 내 응용 프로그램에는 여전히 너무 느립니다.

그래서 사람들은 내가 세상에서 가장 빠른 인텔 기반 비트 플리퍼를 제시 할 수있게 해줍니다. 에 시계 :

비트 플립 시간 400000000 바이트 : 0.050082 초 !!!!!

// Bitflip using AVX2 - The fastest Intel based bitflip in the world!!
// Made by Anders Cedronius 2014 (anders.cedronius (you know what) gmail.com)

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <omp.h>

using namespace std;

#define DISPLAY_HEIGHT  4
#define DISPLAY_WIDTH   32
#define NUM_DATA_BYTES  400000000

// Constants (first we got the mask, then the high order nibble look up table and last we got the low order nibble lookup table)
__attribute__ ((aligned(32))) static unsigned char k1[32*3]={
        0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
        0x00,0x08,0x04,0x0c,0x02,0x0a,0x06,0x0e,0x01,0x09,0x05,0x0d,0x03,0x0b,0x07,0x0f,0x00,0x08,0x04,0x0c,0x02,0x0a,0x06,0x0e,0x01,0x09,0x05,0x0d,0x03,0x0b,0x07,0x0f,
        0x00,0x80,0x40,0xc0,0x20,0xa0,0x60,0xe0,0x10,0x90,0x50,0xd0,0x30,0xb0,0x70,0xf0,0x00,0x80,0x40,0xc0,0x20,0xa0,0x60,0xe0,0x10,0x90,0x50,0xd0,0x30,0xb0,0x70,0xf0
};

// The data to be bitflipped (+32 to avoid the quantization out of memory problem)
__attribute__ ((aligned(32))) static unsigned char data[NUM_DATA_BYTES+32]={};

extern "C" {
void bitflipbyte(unsigned char[],unsigned int,unsigned char[]);
}

int main()
{

    for(unsigned int i = 0; i < NUM_DATA_BYTES; i++)
    {
        data[i] = rand();
    }

    printf ("\r\nData in(start):\r\n");
    for (unsigned int j = 0; j < 4; j++)
    {
        for (unsigned int i = 0; i < DISPLAY_WIDTH; i++)
        {
            printf ("0x%02x,",data[i+(j*DISPLAY_WIDTH)]);
        }
        printf ("\r\n");
    }

    printf ("\r\nNumber of 32-byte chunks to convert: %d\r\n",(unsigned int)ceil(NUM_DATA_BYTES/32.0));

    double start_time = omp_get_wtime();
    bitflipbyte(data,(unsigned int)ceil(NUM_DATA_BYTES/32.0),k1);
    double end_time = omp_get_wtime();

    printf ("\r\nData out:\r\n");
    for (unsigned int j = 0; j < 4; j++)
    {
        for (unsigned int i = 0; i < DISPLAY_WIDTH; i++)
        {
            printf ("0x%02x,",data[i+(j*DISPLAY_WIDTH)]);
        }
        printf ("\r\n");
    }
    printf("\r\n\r\nTime to bitflip %d bytes: %f seconds\r\n\r\n",NUM_DATA_BYTES, end_time-start_time);

    // return with no errors
    return 0;
}

printf는 디버깅을위한 것입니다.

다음은 핵심입니다.

bits 64
global bitflipbyte

bitflipbyte:    
        vmovdqa     ymm2, [rdx]
        add         rdx, 20h
        vmovdqa     ymm3, [rdx]
        add         rdx, 20h
        vmovdqa     ymm4, [rdx]
bitflipp_loop:
        vmovdqa     ymm0, [rdi] 
        vpand       ymm1, ymm2, ymm0 
        vpandn      ymm0, ymm2, ymm0 
        vpsrld      ymm0, ymm0, 4h 
        vpshufb     ymm1, ymm4, ymm1 
        vpshufb     ymm0, ymm3, ymm0         
        vpor        ymm0, ymm0, ymm1
        vmovdqa     [rdi], ymm0
        add     rdi, 20h
        dec     rsi
        jnz     bitflipp_loop
        ret

이 코드는 32 바이트를 차지하고 니블을 마스킹합니다. 높은 니블은 4만큼 오른쪽으로 이동합니다. 그런 다음 vpshufb와 ymm4 / ymm3을 조회 테이블로 사용합니다. 단일 조회 테이블을 사용할 수는 있지만 니블을 다시 OR하기 전에 왼쪽으로 이동해야합니다.

비트를 뒤집는 더 빠른 방법이 있습니다. 그러나 나는 단일 스레드와 CPU에 묶여있어 이것이 달성 할 수있는 가장 빠릅니다. 더 빠른 버전을 만들 수 있습니까?

Intel C / C ++ Compiler 내장 동등 명령 사용에 대해서는 언급하지 마십시오 ...


2
이보다 FAR이 더 많은 투표를받을 자격이 있습니다. 나는 이것으로 할 수 있어야한다는 것을 알고 있었다 pshub. 왜냐하면 모든 최고의 popcount가 그것으로도 완료 되었기 때문이다! 당신을 위해서가 아니라면 여기에 썼을 것입니다. 명성.
Idonotexist Idonotexist

3
감사! 'popcnt'는 내 가장 좋아하는 주제입니다.) 내 BMI2 버전을 확인하십시오. result = __ tzcnt_u64 (~ _pext_u64 (data [i], data [i]));
Anders Cedronius

3
asm 파일의 이름을 bitflip_asm.s로 지정한 다음 : yasm -f elf64 bitflip_asm.s c 파일의 이름을 bitflip.c로 다음과 같이 지정하십시오. g ++ -fopenmp bitflip.c bitflip_asm.o -o bitflip 그게 다입니다.
앤더스 세드 로니 우스

4
인텔 CPU는 포트 1 popcnt에서 tzcnt, 및 pext모두에 대한 실행 단위를 갖습니다 . 따라서 모든 처리량 pext또는 tzcnt비용이 발생합니다 popcnt. 데이터가 L1D 캐시에서 핫한 경우 인텔 CPU에서 어레이를 가장 빠르게 계산하는 방법은 AVX2 pshufb를 사용하는 것입니다. (Ryzen은 클록 당 4 개의 popcnt처리량을 가지므로 아마도 최적이지만 Bulldozer 제품군은 4 개의 클록 popcnt r64,r64처리량 당 하나를 가지고 있습니다 ... agner.org/optimize ).
Peter Cordes

4
나는 내장 버전을 사용하고 있습니다. 그러나 내가 대답했을 때 나는 내가 가진 것을 게시했고 이전 게시물에서 어셈블러를 작성하자마자 똑똑한 알렉은 항상 내가 본질적으로 그것을해야한다고 지적했다. 개발할 때 어셈블러를 먼저 작성하고 결과가 마음에 들면 내장 함수로 옮깁니다. 저입니다. 나는 '테스트'어셈블러 버전 만 가지고있을 때 답변을 게시했습니다.
Anders Cedronius

16

이것은 재귀를 좋아하는 사람들을위한 또 다른 솔루션입니다.

아이디어는 간단하다. 입력을 반으로 나누고 두 개의 반쪽을 바꾸고 단일 비트에 도달 할 때까지 계속하십시오.

Illustrated in the example below.

Ex : If Input is 00101010   ==> Expected output is 01010100

1. Divide the input into 2 halves 
    0010 --- 1010

2. Swap the 2 Halves
    1010     0010

3. Repeat the same for each half.
    10 -- 10 ---  00 -- 10
    10    10      10    00

    1-0 -- 1-0 --- 1-0 -- 0-0
    0 1    0 1     0 1    0 0

Done! Output is 01010100

다음은이를 해결하기위한 재귀 함수입니다. (참고 부호없는 정수를 사용 했으므로 sizeof (unsigned int) * 8 비트까지의 입력에 사용할 수 있습니다.

재귀 함수에는 2 개의 매개 변수가 있습니다. 비트를 반전해야하는 값과 값의 비트 수입니다.

int reverse_bits_recursive(unsigned int num, unsigned int numBits)
{
    unsigned int reversedNum;;
    unsigned int mask = 0;

    mask = (0x1 << (numBits/2)) - 1;

    if (numBits == 1) return num;
    reversedNum = reverse_bits_recursive(num >> numBits/2, numBits/2) |
                   reverse_bits_recursive((num & mask), numBits/2) << numBits/2;
    return reversedNum;
}

int main()
{
    unsigned int reversedNum;
    unsigned int num;

    num = 0x55;
    reversedNum = reverse_bits_recursive(num, 8);
    printf ("Bit Reversal Input = 0x%x Output = 0x%x\n", num, reversedNum);

    num = 0xabcd;
    reversedNum = reverse_bits_recursive(num, 16);
    printf ("Bit Reversal Input = 0x%x Output = 0x%x\n", num, reversedNum);

    num = 0x123456;
    reversedNum = reverse_bits_recursive(num, 24);
    printf ("Bit Reversal Input = 0x%x Output = 0x%x\n", num, reversedNum);

    num = 0x11223344;
    reversedNum = reverse_bits_recursive(num,32);
    printf ("Bit Reversal Input = 0x%x Output = 0x%x\n", num, reversedNum);
}

이것은 출력입니다.

Bit Reversal Input = 0x55 Output = 0xaa
Bit Reversal Input = 0xabcd Output = 0xb3d5
Bit Reversal Input = 0x123456 Output = 0x651690
Bit Reversal Input = 0x11223344 Output = 0x22cc4488

이 방법이 24 비트 예제 (3)에서 작동하지 않습니까? 나는 C와 비트 연산자에 익숙하지 않지만 접근 방식에 대한 설명에서 24-> 12-> 6-> 3 (3 비트가 고르지 않음)을 추측합니다. numBitsint와 마찬가지로 함수 매개 변수에 대해 3을 2로 나누면 1로 내림됩니까?
Brennan

13

글쎄, 이것은 확실히 Matt J와 같은 대답은 아니지만 여전히 유용 할 것입니다.

size_t reverse(size_t n, unsigned int bytes)
{
    __asm__("BSWAP %0" : "=r"(n) : "0"(n));
    n >>= ((sizeof(size_t) - bytes) * 8);
    n = ((n & 0xaaaaaaaaaaaaaaaa) >> 1) | ((n & 0x5555555555555555) << 1);
    n = ((n & 0xcccccccccccccccc) >> 2) | ((n & 0x3333333333333333) << 2);
    n = ((n & 0xf0f0f0f0f0f0f0f0) >> 4) | ((n & 0x0f0f0f0f0f0f0f0f) << 4);
    return n;
}

이것은 64 비트 숫자의 바이트 (비트가 아닌)를 교환하는 BSWAP라는이 작은 명령이 있다는 것을 제외하고 Matt의 최고의 알고리즘과 정확히 동일합니다. 따라서 b7, b6, b5, b4, b3, b2, b1, b0은 b0, b1, b2, b3, b4, b5, b6, b7이됩니다. 32 비트 숫자로 작업하기 때문에 바이트 스왑 된 숫자를 32 비트 아래로 이동해야합니다. 이것은 우리에게 각 바이트의 8 비트와 짜잔을 교환하는 작업으로 우리를 떠납니다! 우리는 끝났습니다.

타이밍 : 내 컴퓨터에서 Matt의 알고리즘은 시행 당 ~ 0.52 초 안에 실행되었습니다. 시험 당 약 0.42 초 안에 광산이 달렸다. 생각보다 20 % 빠르면 나쁘지 않습니다.

명령어 BSWAP의 사용 가능성에 대해 걱정하는 경우 Wikipedia 는 명령어 BSWAP가 1989 년에 나온 80846에 추가 된 것으로 나열합니다. 내 컴퓨터의 경우 64 비트 레지스터에서만 작동합니다.

이 메소드는 모든 정수 데이터 유형에 대해 동일하게 작동하므로 원하는 바이트 수를 전달하여 사소하게 일반화 할 수 있습니다.

    size_t reverse(size_t n, unsigned int bytes)
    {
        __asm__("BSWAP %0" : "=r"(n) : "0"(n));
        n >>= ((sizeof(size_t) - bytes) * 8);
        n = ((n & 0xaaaaaaaaaaaaaaaa) >> 1) | ((n & 0x5555555555555555) << 1);
        n = ((n & 0xcccccccccccccccc) >> 2) | ((n & 0x3333333333333333) << 2);
        n = ((n & 0xf0f0f0f0f0f0f0f0) >> 4) | ((n & 0x0f0f0f0f0f0f0f0f) << 4);
        return n;
    }

그러면 다음과 같이 호출 될 수 있습니다.

    n = reverse(n, sizeof(char));//only reverse 8 bits
    n = reverse(n, sizeof(short));//reverse 16 bits
    n = reverse(n, sizeof(int));//reverse 32 bits
    n = reverse(n, sizeof(size_t));//reverse 64 bits

컴파일러는 여분의 매개 변수를 최적화 할 수 있어야하며 (컴파일러가 함수를 인라인한다고 가정) sizeof(size_t)오른쪽 시프트는 완전히 제거됩니다. GCC는 적어도 BSWAP를 제거 할 수 없으며 통과하면 오른쪽 이동을 수행 할 수 없습니다 sizeof(char).


2
Intel Instruction Set Reference Volume 2A ( intel.com/content/www/us/en/processors/… )에 따르면 두 개의 BSWAP 명령어가 있습니다 : BSWAP r32 (32 비트 레지스터에서 작동), 0F C8 + rd로 인코딩 됨 REX.W + 0F C8 + rd로 인코딩 된 BSWAP r64 (64 비트 레지스터에서 작동).
Nubok

"n = reverse (n, sizeof (size_t)); // reverse 64 bits"와 같이 사용할 수 있지만 모든 상수가 64 비트로 확장되지 않으면 32 비트의 결과 만 제공합니다.
rajkosto

C ++ 11부터 @rajkosto는 허용되는 정수 리터럴 유형을 포함 unsigned long long int합니다. 여기 에는 여기에
SirGuy

확인? 64 비트 값 에서이 작업을 수행하려면 리터럴을 확장해야합니다 (예 : 0xf0f0f0f0f0f0f0f0ull). 그렇지 않으면 결과의 높은 32 비트가 모두 0이됩니다.
rajkosto

@rajkosto 아, 나는 당신의 첫 번째 의견을 오해했다, 나는 그것을 고쳤다
SirGuy

13

Anders Cedronius의 답변 은 AVX2를 지원하는 x86 CPU를 가진 사람들에게 훌륭한 솔루션을 제공합니다. AVX를 지원하지 않는 x86 플랫폼 또는 x86 이외의 플랫폼의 경우 다음 구현 중 하나가 제대로 작동합니다.

첫 번째 코드는 클래식 이진 파티셔닝 방법의 변형이며 다양한 ARM 프로세서에 유용한 shift-plus-logic 관용구를 최대한 활용하도록 코딩되었습니다. 또한 각 32 비트 마스크 값을로드하기 위해 여러 명령이 필요한 RISC 프로세서에 유리한 온더 플라이 마스크 생성 기능을 사용합니다. x86 플랫폼 용 컴파일러는 지속적인 전파를 사용하여 런타임보다는 컴파일 타임에 모든 마스크를 계산해야합니다.

/* Classic binary partitioning algorithm */
inline uint32_t brev_classic (uint32_t a)
{
    uint32_t m;
    a = (a >> 16) | (a << 16);                            // swap halfwords
    m = 0x00ff00ff; a = ((a >> 8) & m) | ((a << 8) & ~m); // swap bytes
    m = m^(m << 4); a = ((a >> 4) & m) | ((a << 4) & ~m); // swap nibbles
    m = m^(m << 2); a = ((a >> 2) & m) | ((a << 2) & ~m);
    m = m^(m << 1); a = ((a >> 1) & m) | ((a << 1) & ~m);
    return a;
}

"컴퓨터 프로그래밍의 기술"4 권에서 D. Knuth는 고전 이진 분할 알고리즘보다 약간 적은 연산이 필요한 비트를 반전시키는 영리한 방법을 보여줍니다. TAOCP에서 찾을 수없는 32 비트 피연산자에 대한 이러한 알고리즘 중 하나 가이 문서 의 Hacker 's Delight 웹 사이트에 나와 있습니다.

/* Knuth's algorithm from http://www.hackersdelight.org/revisions.pdf. Retrieved 8/19/2015 */
inline uint32_t brev_knuth (uint32_t a)
{
    uint32_t t;
    a = (a << 15) | (a >> 17);
    t = (a ^ (a >> 10)) & 0x003f801f; 
    a = (t + (t << 10)) ^ a;
    t = (a ^ (a >>  4)) & 0x0e038421; 
    a = (t + (t <<  4)) ^ a;
    t = (a ^ (a >>  2)) & 0x22488842; 
    a = (t + (t <<  2)) ^ a;
    return a;
}

Intel 컴파일러 C / C ++ 컴파일러 13.1.3.198을 사용하면 위의 두 함수가 모두 훌륭한 XMM레지스터를 자동 벡터화 합니다. 많은 노력 없이도 수동으로 벡터화 할 수 있습니다.

자동 벡터화 된 코드를 사용하는 IvyBridge Xeon E3 1270v2에서 1 억 uint32_t단어는을 사용하여 0.070 초 brev_classic(),을 사용하여 0.068 초 에 비트 반전되었습니다 brev_knuth(). 벤치 마크가 시스템 메모리 대역폭에 의해 제한되지 않도록주의를 기울였습니다.


2
@JoelSnyder "많은 매직 넘버"로 가정합니다 brev_knuth(). Hacker 's Delight의 PDF 속성에 따르면이 숫자는 Knuth 자신이 직접 작성한 것 같습니다. 상수가 어떻게 도출되었는지, 또는 임의의 단어 크기에 대한 상수 및 변화 인자를 도출하는 방법에 대해 충분히 설명하기 위해 TAOCP의 기본 설계 원칙에 대한 Knuth의 설명을 이해했다고 주장 할 수 없습니다.
njuffa

8

비트 배열이 있다고 가정하면 다음과 같은 이점이 있습니다. 1. MSB부터 비트를 하나씩 스택에 넣습니다. 2.이 스택의 팝 비트를 다른 어레이 (또는 공간을 절약하려는 경우 동일한 어레이)로 팝하고 첫 번째 팝된 비트를 MSB에 배치 한 다음 그보다 덜 중요한 비트로 진행합니다.

Stack stack = new Stack();
Bit[] bits = new Bit[] { 0, 0, 1, 0, 0, 0, 0, 0 };

for (int i = 0; i < bits.Length; i++) 
{
    stack.push(bits[i]);
}

for (int i = 0; i < bits.Length; i++)
{
    bits[i] = stack.pop();
}

3
이것은 나를 미소 짓게했다 :) 나는 최적화 된 C에서 위에서 설명한 것들 중 하나에 대해이 C # 솔루션의 벤치 마크를보고 싶다.
Matt J

LOL ...하지만 이봐! '최고의 알고리즘'에서 형용사 '최고'는 꽤 주관적인 것입니다 : D
Frederick The Fool

7

기본 ARM 명령어 "rbit"는 1 CPU 사이클과 1 개의 추가 CPU 레지스터로 수행 할 수 있으며 이길 수 없습니다.


6

이것은 인간에게는 직업이 아닙니다! ...하지만 기계에 완벽

이 질문이 처음 제기 된 지 6 년이 지난 2015 년입니다. 그 후 컴파일러는 우리의 주인이되었으며, 인간으로서의 우리의 일은 단지 그들을 돕는 것입니다. 머신에 의도를 부여하는 가장 좋은 방법은 무엇입니까?

비트 리버설 (bit-reversal)은 매우 일반적이기 때문에 x86의 ISA가 왜 커지고 있는지에 대한 지침이없는 이유를 궁금해해야합니다.

그 이유 : 컴파일러에 진정한 간결한 의도를 부여하면 비트 리버설은 ~ 20 CPU 사이클 만 소요 됩니다 . reverse ()를 작성하고 사용하는 방법을 보여 드리겠습니다.

#include <inttypes.h>
#include <stdio.h>

uint64_t reverse(const uint64_t n,
                 const uint64_t k)
{
        uint64_t r, i;
        for (r = 0, i = 0; i < k; ++i)
                r |= ((n >> i) & 1) << (k - i - 1);
        return r;
}

int main()
{
        const uint64_t size = 64;
        uint64_t sum = 0;
        uint64_t a;
        for (a = 0; a < (uint64_t)1 << 30; ++a)
                sum += reverse(a, size);
        printf("%" PRIu64 "\n", sum);
        return 0;
}

Clang 버전> = 3.6, -O3, -march = native (Haswell에서 테스트)로이 샘플 프로그램을 컴파일하면 새로운 AVX2 명령어를 사용하여 11 초의 런타임으로 ~ 10 억 reverse ()를 처리 하여 아트 워크 품질의 코드를 얻을 수 있습니다. 2GHz가 가정하면 20 CPU 사이클을 달콤한 것으로 가정하면 0.5 ns CPU 사이클로 reverse () 당 ~ 10 ns입니다.

  • 하나의 큰 어레이에서 RAM에 한 번 액세스하는 데 걸리는 시간에 10 개의 reverse ()를 맞출 수 있습니다!
  • L2 캐시 LUT에 두 번 액세스하는 데 걸리는 시간에 1 reverse ()를 맞출 수 있습니다.

주의 사항 :이 샘플 코드는 몇 년 동안 괜찮은 벤치 마크가되어야하지만, 컴파일러가 main ()을 최적화하여 실제 결과를 계산하는 대신 최종 결과를 인쇄 할 수있을 정도로 똑똑해지면 결국 그 시대를 보여줄 것입니다. 그러나 지금은 reverse ()를 보여줍니다.


Bit-reversal is so common...나는 그것에 대해 모른다. 나는 거의 매일 비트 수준의 데이터를 처리하는 코드를 사용하며, 이러한 특정 요구가 있었음을 기억할 수 없습니다. 어떤 시나리오에서 필요합니까? -자체적으로 해결하는 것은 흥미로운 문제가 아닙니다.
500-내부 서버 오류

@ 500-InternalServerError 나는 빠르고 간결한 데이터 구조로 문법 추론 에서이 기능을 여러 번 필요로합니다. 비트 어레이로 인코딩 된 일반 이진 트리는 문법을 "빅 엔디안"순서로 유추합니다. 그러나 비트 리버설 순열로 노드가 교체 된 트리 (비트 어레이)를 구축하면 일반화가 향상되기 위해 학습 된 문법 문자열은 "little endian"입니다. 이 전환을 통해 고정 정수 크기가 아닌 가변 길이 문자열을 유추 할 수 있습니다. 이 상황은 효율적인 FFT에서도 많이 나타납니다. en.wikipedia.org/wiki/Bit-reversal_permutation

1
고맙게도, FFT가 귀하의 답변에 관여 할 수도 있음을 직감적으로 처리했습니다 :)
500-Internal Server Error

왜 단지 20주기입니까? 어떤 아키텍처? 인류와 우리의 후손이 모두 죽을 때까지 미래의 모든 초 광대역 VLIW 아키텍처에서 이것이 사실입니까? 그냥 질문, 대답이 없습니다 ... 다시 지옥으로
하향


5

나는 그것이 C가 아니라 asm이라는 것을 안다.

var1 dw 0f0f0
clc
     push ax
     push cx
     mov cx 16
loop1:
     shl var1
     shr ax
loop loop1
     pop ax
     pop cx

이것은 캐리 비트와 함께 작동하므로 플래그를 저장할 수도 있습니다.


1
나는 당신이 asm 키워드를 사용할 수 있다고 생각합니다 .
tom

이것은 심지어 작동하지 않습니다. 플래그를 읽지 않는 대신 rclCF를로 바꾸고 싶다고 생각합니다 . (또는 ). 이 수정으로도 느린 명령을 사용 하고 메모리에 보관 하면 엄청나게 느립니다 ! 실제로 이것이 AX에서 출력을 생성한다고 생각되지만 결과 위에 AX의 이전 값을 저장 / 복원합니다. var1shladc dx,dxloopvar1
Peter Cordes

4

적은 메모리와 가장 빠른 구현.

private Byte  BitReverse(Byte bData)
    {
        Byte[] lookup = { 0, 8,  4, 12, 
                          2, 10, 6, 14 , 
                          1, 9,  5, 13,
                          3, 11, 7, 15 };
        Byte ret_val = (Byte)(((lookup[(bData & 0x0F)]) << 4) + lookup[((bData & 0xF0) >> 4)]);
        return ret_val;
    }

4

이것은 기본적으로 첫 번째 "reverse ()"와 동일하지만 64 비트이며 명령 스트림에서로드 할 즉각적 마스크가 하나만 필요합니다. GCC는 점프없이 코드를 생성하므로 매우 빠릅니다.

#include <stdio.h>

static unsigned long long swap64(unsigned long long val)
{
#define ZZZZ(x,s,m) (((x) >>(s)) & (m)) | (((x) & (m))<<(s));
/* val = (((val) >>16) & 0xFFFF0000FFFF) | (((val) & 0xFFFF0000FFFF)<<16); */

val = ZZZZ(val,32,  0x00000000FFFFFFFFull );
val = ZZZZ(val,16,  0x0000FFFF0000FFFFull );
val = ZZZZ(val,8,   0x00FF00FF00FF00FFull );
val = ZZZZ(val,4,   0x0F0F0F0F0F0F0F0Full );
val = ZZZZ(val,2,   0x3333333333333333ull );
val = ZZZZ(val,1,   0x5555555555555555ull );

return val;
#undef ZZZZ
}

int main(void)
{
unsigned long long val, aaaa[16] =
 { 0xfedcba9876543210,0xedcba9876543210f,0xdcba9876543210fe,0xcba9876543210fed
 , 0xba9876543210fedc,0xa9876543210fedcb,0x9876543210fedcba,0x876543210fedcba9
 , 0x76543210fedcba98,0x6543210fedcba987,0x543210fedcba9876,0x43210fedcba98765
 , 0x3210fedcba987654,0x210fedcba9876543,0x10fedcba98765432,0x0fedcba987654321
 };
unsigned iii;

for (iii=0; iii < 16; iii++) {
    val = swap64 (aaaa[iii]);
    printf("A[]=%016llX Sw=%016llx\n", aaaa[iii], val);
    }
return 0;
}

4

나는 명백한 원시 회전이 얼마나 빠를 지 궁금했다. 내 컴퓨터 (i7 @ 2600)에서 1,500,150,000 반복의 평균은 27.28 ns(131,071 64 비트 정수의 임의 세트 이상)입니다.

장점 : 필요한 메모리 양이 적고 코드가 간단합니다. 나는 그것이 그렇게 크지 않다고 말할 것입니다. 필요한 시간은 모든 입력에 대해 예측 가능하고 일정합니다 (128 산술 SHIFT 연산 + 64 논리 AND 연산 + 64 논리 OR 연산).

나는 @Matt J가 얻은 최고의 시간과 비교했습니다. 그의 대답을 올바르게 읽으면 그가 얻은 최고는 반복에 0.631739대한 초였습니다 1,000,000. 이것은 평균 631 ns회전 당입니다.

내가 사용한 코드 스 니펫은 다음과 같습니다.

unsigned long long reverse_long(unsigned long long x)
{
    return (((x >> 0) & 1) << 63) |
           (((x >> 1) & 1) << 62) |
           (((x >> 2) & 1) << 61) |
           (((x >> 3) & 1) << 60) |
           (((x >> 4) & 1) << 59) |
           (((x >> 5) & 1) << 58) |
           (((x >> 6) & 1) << 57) |
           (((x >> 7) & 1) << 56) |
           (((x >> 8) & 1) << 55) |
           (((x >> 9) & 1) << 54) |
           (((x >> 10) & 1) << 53) |
           (((x >> 11) & 1) << 52) |
           (((x >> 12) & 1) << 51) |
           (((x >> 13) & 1) << 50) |
           (((x >> 14) & 1) << 49) |
           (((x >> 15) & 1) << 48) |
           (((x >> 16) & 1) << 47) |
           (((x >> 17) & 1) << 46) |
           (((x >> 18) & 1) << 45) |
           (((x >> 19) & 1) << 44) |
           (((x >> 20) & 1) << 43) |
           (((x >> 21) & 1) << 42) |
           (((x >> 22) & 1) << 41) |
           (((x >> 23) & 1) << 40) |
           (((x >> 24) & 1) << 39) |
           (((x >> 25) & 1) << 38) |
           (((x >> 26) & 1) << 37) |
           (((x >> 27) & 1) << 36) |
           (((x >> 28) & 1) << 35) |
           (((x >> 29) & 1) << 34) |
           (((x >> 30) & 1) << 33) |
           (((x >> 31) & 1) << 32) |
           (((x >> 32) & 1) << 31) |
           (((x >> 33) & 1) << 30) |
           (((x >> 34) & 1) << 29) |
           (((x >> 35) & 1) << 28) |
           (((x >> 36) & 1) << 27) |
           (((x >> 37) & 1) << 26) |
           (((x >> 38) & 1) << 25) |
           (((x >> 39) & 1) << 24) |
           (((x >> 40) & 1) << 23) |
           (((x >> 41) & 1) << 22) |
           (((x >> 42) & 1) << 21) |
           (((x >> 43) & 1) << 20) |
           (((x >> 44) & 1) << 19) |
           (((x >> 45) & 1) << 18) |
           (((x >> 46) & 1) << 17) |
           (((x >> 47) & 1) << 16) |
           (((x >> 48) & 1) << 15) |
           (((x >> 49) & 1) << 14) |
           (((x >> 50) & 1) << 13) |
           (((x >> 51) & 1) << 12) |
           (((x >> 52) & 1) << 11) |
           (((x >> 53) & 1) << 10) |
           (((x >> 54) & 1) << 9) |
           (((x >> 55) & 1) << 8) |
           (((x >> 56) & 1) << 7) |
           (((x >> 57) & 1) << 6) |
           (((x >> 58) & 1) << 5) |
           (((x >> 59) & 1) << 4) |
           (((x >> 60) & 1) << 3) |
           (((x >> 61) & 1) << 2) |
           (((x >> 62) & 1) << 1) |
           (((x >> 63) & 1) << 0);
}

@greybeard 나는 당신의 질문을 이해하지 못합니다.
marian adam

버그를 알아 주셔서 감사합니다. 제공된 코드 샘플을 수정했습니다.
marian adam

3

표준 템플릿 라이브러리를 사용할 수 있습니다. 위에서 언급 한 코드보다 느릴 수 있습니다. 그러나 더 명확하고 이해하기 쉬운 것 같습니다.

 #include<bitset>
 #include<iostream>


 template<size_t N>
 const std::bitset<N> reverse(const std::bitset<N>& ordered)
 {
      std::bitset<N> reversed;
      for(size_t i = 0, j = N - 1; i < N; ++i, --j)
           reversed[j] = ordered[i];
      return reversed;
 };


 // test the function
 int main()
 {
      unsigned long num; 
      const size_t N = sizeof(num)*8;

      std::cin >> num;
      std::cout << std::showbase << std::hex;
      std::cout << "ordered  = " << num << std::endl;
      std::cout << "reversed = " << reverse<N>(num).to_ulong()  << std::endl;
      std::cout << "double_reversed = " << reverse<N>(reverse<N>(num)).to_ulong() << std::endl;  
 }

2

일반적인

C 코드. 1 바이트 입력 데이터 수를 예로 사용합니다.

    unsigned char num = 0xaa;   // 1010 1010 (aa) -> 0101 0101 (55)
    int s = sizeof(num) * 8;    // get number of bits
    int i, x, y, p;
    int var = 0;                // make var data type to be equal or larger than num

    for (i = 0; i < (s / 2); i++) {
        // extract bit on the left, from MSB
        p = s - i - 1;
        x = num & (1 << p);
        x = x >> p;
        printf("x: %d\n", x);

        // extract bit on the right, from LSB
        y = num & (1 << i);
        y = y >> i;
        printf("y: %d\n", y);

        var = var | (x << i);       // apply x
        var = var | (y << p);       // apply y
    }

    printf("new: 0x%x\n", new);

이 질문은 "단순 / 간단한"것이 아니라 "가장 효율적인"을 요구했다.
Peter Cordes

1

다음은 어떻습니까 :

    uint reverseMSBToLSB32ui(uint input)
    {
        uint output = 0x00000000;
        uint toANDVar = 0;
        int places = 0;

        for (int i = 1; i < 32; i++)
        {
            places = (32 - i);
            toANDVar = (uint)(1 << places);
            output |= (uint)(input & (toANDVar)) >> places;

        }


        return output;
    }

작고 쉬움 (32 비트 만 해당).


이 질문은 "가장 효율적"이라고 물었다. 루핑을 32 번 배제 할 수 있습니다. (특히 마스크를 움직이지 않고 결과를 LSB로
낮추지

1

나는 이것이 비트를 뒤집는 가장 간단한 방법 중 하나라고 생각했다. 이 논리에 결함이 있으면 알려주십시오. 기본적으로이 논리에서 비트의 위치 값을 확인합니다. 반전 위치에서 값이 1이면 비트를 설정하십시오.

void bit_reverse(ui32 *data)
{
  ui32 temp = 0;    
  ui32 i, bit_len;    
  {    
   for(i = 0, bit_len = 31; i <= bit_len; i++)   
   {    
    temp |= (*data & 1 << i)? (1 << bit_len-i) : 0;    
   }    
   *data = temp;    
  }    
  return;    
}    

이 질문은 "단순 / 간단한"것이 아니라 "가장 효율적인"을 요구했다.
Peter Cordes

0
unsigned char ReverseBits(unsigned char data)
{
    unsigned char k = 0, rev = 0;

    unsigned char n = data;

    while(n)

    {
        k = n & (~(n - 1));
        n &= (n - 1);
        rev |= (128 / k);
    }
    return rev;
}

흥미롭지 만 런타임 변수로 나누기가 느립니다. k항상 2의 거듭 제곱이지만 컴파일러는이를 입증하지 않고 비트 스캔 / 시프트로 바꿉니다.
Peter Cordes

0

내가 아는 가장 간단한 방법은 다음과 같습니다. MSB입력되고 LSB'역전 된'출력입니다.

unsigned char rev(char MSB) {
    unsigned char LSB=0;  // for output
    _FOR(i,0,8) {
        LSB= LSB << 1;
        if(MSB&1) LSB = LSB | 1;
        MSB= MSB >> 1;
    }
    return LSB;
}

//    It works by rotating bytes in opposite directions. 
//    Just repeat for each byte.

0
// Purpose: to reverse bits in an unsigned short integer 
// Input: an unsigned short integer whose bits are to be reversed
// Output: an unsigned short integer with the reversed bits of the input one
unsigned short ReverseBits( unsigned short a )
{
     // declare and initialize number of bits in the unsigned short integer
     const char num_bits = sizeof(a) * CHAR_BIT;

     // declare and initialize bitset representation of integer a
     bitset<num_bits> bitset_a(a);          

     // declare and initialize bitset representation of integer b (0000000000000000)
     bitset<num_bits> bitset_b(0);                  

     // declare and initialize bitset representation of mask (0000000000000001)
     bitset<num_bits> mask(1);          

     for ( char i = 0; i < num_bits; ++i )
     {
          bitset_b = (bitset_b << 1) | bitset_a & mask;
          bitset_a >>= 1;
     }

     return (unsigned short) bitset_b.to_ulong();
}

void PrintBits( unsigned short a )
{
     // declare and initialize bitset representation of a
     bitset<sizeof(a) * CHAR_BIT> bitset(a);

     // print out bits
     cout << bitset << endl;
}


// Testing the functionality of the code

int main ()
{
     unsigned short a = 17, b;

     cout << "Original: "; 
     PrintBits(a);

     b = ReverseBits( a );

     cout << "Reversed: ";
     PrintBits(b);
}

// Output:
Original: 0000000000010001
Reversed: 1000100000000000

0

숫자가 적을 때 빠르게 종료되는 다른 루프 기반 솔루션 (여러 유형의 경우 C ++에서)

template<class T>
T reverse_bits(T in) {
    T bit = static_cast<T>(1) << (sizeof(T) * 8 - 1);
    T out;

    for (out = 0; bit && in; bit >>= 1, in >>= 1) {
        if (in & 1) {
            out |= bit;
        }
    }
    return out;
}

또는 부호없는 int의 경우 C

unsigned int reverse_bits(unsigned int in) {
    unsigned int bit = 1u << (sizeof(T) * 8 - 1);
    unsigned int out;

    for (out = 0; bit && in; bit >>= 1, in >>= 1) {
        if (in & 1)
            out |= bit;
    }
    return out;
}

0

다른 많은 게시물은 속도에 대해 우려하는 것 같습니다 (즉 가장 빠름 = 가장 빠름). 단순성은 어떻습니까? 치다:

char ReverseBits(char character) {
    char reversed_character = 0;
    for (int i = 0; i < 8; i++) {
        char ith_bit = (c >> i) & 1;
        reversed_character |= (ith_bit << (sizeof(char) - 1 - i));
    }
    return reversed_character;
}

영리한 컴파일러가 당신을 위해 최적화되기를 바랍니다.

더 긴 비트 목록 (비트 포함 sizeof(char) * n) 을 반대로 바꾸려면 이 함수를 사용하여 다음을 얻을 수 있습니다.

void ReverseNumber(char* number, int bit_count_in_number) {
    int bytes_occupied = bit_count_in_number / sizeof(char);      

    // first reverse bytes
    for (int i = 0; i <= (bytes_occupied / 2); i++) {
        swap(long_number[i], long_number[n - i]);
    }

    // then reverse bits of each individual byte
    for (int i = 0; i < bytes_occupied; i++) {
         long_number[i] = ReverseBits(long_number[i]);
    }
}

이것은 [10000000, 10101010]을 [01010101, 00000001]로 되돌릴 것입니다.


내부 루프에는 3 개의 시프트가 있습니다. 로 저장하십시오 ith_bit = (c >> i) & 1. 또한 대상 레지스터에서 n 번째 비트를 설정하기 위해 reversed_charx86에서 sub something/ bts reg,reg로 컴파일되기를 기대하지 않는 한 비트를 이동하는 대신 이동하여 SUB를 저장하십시오 .
Peter Cordes

-1

의사 코드의 비트 반전

소스-> 반전 될 바이트 b00101100 대상-> 반전 됨, 부호없는 유형이어야하므로 부호 비트가 전파되지 않음

원본에 영향을 미치지 않도록 임시로 복사하고 부호 비트가 자동으로 이동되지 않도록 부호없는 유형이어야합니다.

bytecopy = b0010110

LOOP8 : // 바이트 카피가 <0 (음수)이면이 8 회 테스트

    set bit8 (msb) of reversed = reversed | b10000000 

else do not set bit8

shift bytecopy left 1 place
bytecopy = bytecopy << 1 = b0101100 result

shift result right 1 place
reversed = reversed >> 1 = b00000000
8 times no then up^ LOOP8
8 times yes then done.

-1

나의 간단한 해결책

BitReverse(IN)
    OUT = 0x00;
    R = 1;      // Right mask   ...0000.0001
    L = 0;      // Left mask    1000.0000...
    L = ~0; 
    L = ~(i >> 1);
    int size = sizeof(IN) * 4;  // bit size

    while(size--){
        if(IN & L) OUT = OUT | R; // start from MSB  1000.xxxx
        if(IN & R) OUT = OUT | L; // start from LSB  xxxx.0001
        L = L >> 1;
        R = R << 1; 
    }
    return OUT;

1
무엇입니까 i? 또한, 그 마법 상수는 * 4무엇입니까? 그렇 CHAR_BIT / 2습니까?
Peter Cordes

-1

이것은 32 비트 용이며 8 비트를 고려하면 크기를 변경해야합니다.

    void bitReverse(int num)
    {
        int num_reverse = 0;
        int size = (sizeof(int)*8) -1;
        int i=0,j=0;
        for(i=0,j=size;i<=size,j>=0;i++,j--)
        {
            if((num >> i)&1)
            {
                num_reverse = (num_reverse | (1<<j));
            }
        }
        printf("\n rev num = %d\n",num_reverse);
    }

LSB-> MSB 순서로 입력 정수 "num"을 읽고 MSB-> LSB 순서로 num_reverse에 저장합니다.


1
이해하기 쉽도록 코드에 설명을 추가해야합니다.
Tunaki

-3
int bit_reverse(int w, int bits)
{
    int r = 0;
    for (int i = 0; i < bits; i++)
    {
        int bit = (w & (1 << i)) >> i;
        r |= bit << (bits - i - 1);
    }
    return r;
}

3
일반적으로 코드의 기능과 문제를 해결하는 이유에 대한 설명이 포함되어 있으면 답변이 훨씬 유용합니다.
IKavanagh
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.