Algoritmo eficiente para reversão de bits (de MSB-> LSB para LSB-> MSB) em C


243

Qual é o algoritmo mais eficiente para obter o seguinte:

0010 0000 => 0000 0100

A conversão é de MSB-> LSB para LSB-> MSB. Todos os bits devem ser revertidos; isto é, não é troca de endianness.


1
Eu acho que o nome apropriado é uma operação bit a bit.
Kredns 14/04/09

5
Eu acho que você quis dizer reversão, não rotação.
Juliano

2
A maioria dos processadores ARM possui uma operação integrada para isso. O ARM Cortex-M0 não, e eu descobri que usar uma tabela por byte para trocar bits é a abordagem mais rápida.
starblue

2
Veja também Bit Twiddling Hacks de Sean Eron Anderson .
JWW

2
Por favor, defina "best"
Lee Taylor

Respostas:


497

NOTA : Todos os algoritmos abaixo estão em C, mas devem ser portáteis para o idioma de sua escolha (apenas não olhe para mim quando não for tão rápido :)

Opções

Pouca memória ( intmáquina de 32 bits , 32 bits) ( daqui ):

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

}

Na famosa página Bit Twiddling Hacks :

Mais rápido (tabela de pesquisa) :

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

Você pode estender essa idéia para ints de 64 bits ou trocar a memória por velocidade (supondo que o cache de dados L1 seja grande o suficiente) e reverter 16 bits por vez com uma tabela de pesquisa de entrada de 64K.


Outras

Simples

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

Mais rápido (processador de 32 bits)

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

Mais rápido (processador de 64 bits)

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

Se você quiser fazer isso em 32 bits int, basta inverter os bits em cada byte e inverter a ordem dos bytes. Isso é:

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

Resultados

Comparei as duas soluções mais promissoras, a tabela de pesquisa e AND bit a bit (a primeira). A máquina de teste é um laptop com 4 GB de DDR2-800 e um Core 2 Duo T7500 a 2,4 GHz, cache L2 de 4 MB; YMMV. Eu usei o gcc 4.3.2 no Linux de 64 bits. O OpenMP (e as ligações do GCC) foram usados ​​para temporizadores de alta resolução.

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

Eu tentei ambas as abordagens em várias otimizações diferentes, executei três tentativas em cada nível e cada uma delas reverteu 100 milhões aleatoriamente unsigned ints. Para a opção de tabela de pesquisa, tentei os dois esquemas (opções 1 e 2) apresentados na página de hacks bit a bit. Os resultados são mostrados abaixo.

AND bit a bit

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

Tabela de pesquisa (opção 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  

Tabela de pesquisa (opção 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

Conclusão

Use a tabela de pesquisa, com a opção 1 (o endereçamento de bytes é surpreendentemente lento) se você estiver preocupado com o desempenho. Se você precisar extrair todo último byte de memória do seu sistema (e você pode, se se preocupa com o desempenho da inversão de bits), as versões otimizadas da abordagem AND bit a bit também não são muito ruins.

Embargo

Sim, eu sei que o código de referência é um hack completo. Sugestões sobre como melhorá-lo são mais que bem-vindas. Coisas que eu sei sobre:

  • Eu não tenho acesso ao ICC. Isso pode ser mais rápido (responda em um comentário se você puder testar isso).
  • Uma tabela de pesquisa de 64K pode funcionar bem em algumas microarquiteturas modernas com L1D grande.
  • -mtune = native não funcionou para -O2 / -O3 ( ldexplodiu com algum erro de redefinição de símbolo maluco), então não acredito que o código gerado esteja ajustado para minha microarquitetura.
  • Pode haver uma maneira de fazer isso um pouco mais rápido com o SSE. Eu não tenho idéia de como, mas com replicação rápida, AND embalado bit a bit e instruções swizzling, deve haver algo lá.
  • Eu sei que apenas o conjunto x86 é perigoso; aqui está o código que o GCC gerou em -O3 para a opção 1, para que alguém com mais conhecimento do que eu possa conferir:

32 bits

.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

Edição: Eu também tentei usar uint64_ttipos na minha máquina para ver se houve algum aumento de desempenho. O desempenho foi cerca de 10% mais rápido que 32 bits e era quase idêntico se você estava apenas usando tipos de 64 bits para reverter bits em dois inttipos de 32 bits por vez ou se você estava realmente invertendo os bits pela metade. valores de bits. O código de montagem é mostrado abaixo (no caso anterior, a reversão de bits para dois inttipos de 32 bits por vez):

.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 para post excessivamente detalhado e completo. j / k. +1.
MPEN

8
Foi um exercício interessante, se não tão gratificante. Se nada mais, espero ver o processo é construtivo para alguém que pode querer referência algo mais meritório :)
Matt J

5
Meu Deus! Acho que encontrei ... o que pode muito bem ser ... um espécime VERDADEIRO. Terei que consultar meus documentos e fazer mais pesquisas, mas algo me diz (Deus, me ajude), que essa é de longe a melhor, mais completa e útil resposta que o Stack Overflow ainda tem. Até John Skeet ficaria ao mesmo tempo chocado e impressionado!
Zeboidlund

3
Lembre-se de que uma falha específica da marca de microbench (entre uma lista de muitas outras) é que ela tende a favorecer artificialmente as soluções baseadas em tabelas de pesquisa. Como o benchmark está repetindo a única operação em um loop, muitas vezes descobrirá que o uso de uma tabela de pesquisa que se encaixa no L1 é o mais rápido, porque tudo ocorrerá no L1 toda vez que não houver pressão no cache. Em um caso de uso real, a operação geralmente será intercalada com outras operações que causam alguma pressão no cache. Uma falha na RAM pode demorar 10 ou 100 vezes mais que o normal, mas isso é ignorado nos benchmarks.
BeeOnRope

2
O resultado é que, se duas soluções estão próximas, geralmente escolho a solução não LUT (ou aquela com a LUT menor) porque o impacto no mundo real de uma LUT pode ser grave. Melhor ainda seria comparar cada solução "in situ" - onde ela é realmente usada em aplicativos maiores, com informações realistas. Obviamente, nem sempre temos tempo para isso e nem sempre sabemos o que é uma entrada realista.
BeeOnRope

80

Esse tópico chamou minha atenção, pois lida com um problema simples que requer muito trabalho (ciclos da CPU), mesmo para uma CPU moderna. E um dia eu também fiquei lá com o mesmo ¤ #% "#" problema. Eu tive que virar milhões de bytes. No entanto, eu sei que todos os meus sistemas de destino são modernos baseados em Intel, então vamos começar a otimizar ao extremo !!!

Então eu usei o código de pesquisa de Matt J como base. o sistema em que estou comparando é um i7 haswell 4700eq.

A pesquisa de Matt J transferiu bits 400 000 000 bytes: cerca de 0,272 segundos.

Fui em frente e tentei ver se o compilador ISPC da Intel poderia vetorizar a aritmética no sentido inverso.c.

Não vou aborrecê-lo com minhas descobertas aqui, já que tentei muito ajudar o compilador a encontrar coisas; de qualquer maneira, acabei com um desempenho de cerca de 0,15 segundos para alterar bit 400 000 000 bytes. É uma grande redução, mas para o meu aplicativo ainda é muito lento ..

Então, as pessoas me permitem apresentar o bitflipper baseado em Intel mais rápido do mundo. Cronometrado em:

Hora de converter bit 400000000 bytes: 0,050082 segundos !!!!!

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

Os printf são para depuração ..

Aqui está o cavalo de batalha:

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

O código ocupa 32 bytes e mascara os petiscos. A mordidela alta é deslocada para a direita em 4. Então eu uso vpshufb e ymm4 / ymm3 como tabelas de pesquisa. Eu poderia usar uma única tabela de pesquisa, mas teria que mudar para a esquerda antes de ORing os petiscos juntos novamente.

Existem maneiras ainda mais rápidas de virar os bits. Mas estou vinculado ao thread único e à CPU, então foi o mais rápido que consegui. Você pode fazer uma versão mais rápida?

Não faça comentários sobre o uso dos comandos equivalentes intrínsecos do compilador Intel C / C ++ ...


2
Você merece MUITO mais votos do que isso. Eu sabia que isso deveria ser possível pshub, porque, afinal, o melhor contador de histórias também é feito! Eu teria escrito aqui se não fosse por você. Parabéns.
Iwillnotexist Idonotexist

3
Obrigado! 'popcnt' é outro assunto favorito dos meus;) Confira minha versão do BMI2: result = __ tzcnt_u64 (~ _pext_u64 (data [i], data [i]));
Anders Cedronius

3
Nomeie o arquivo asm: bitflip_asm.s então: yasm -f elf64 bitflip_asm.s Nomeie o arquivo c: bitflip.c então: g ++ -fopenmp bitflip.c bitflip_asm.o -o bitflip É isso.
Anders Cedronius

4
Intel CPUs têm as unidades de execução para popcnt, tzcnte pexttudo na porta 1. Então, toda pextoutzcnt custa um popcntrendimento. Se seus dados estiverem quentes no cache L1D, a maneira mais rápida de contar uma matriz nas CPUs Intel é com o AVX2 pshufb. (A Ryzen tem uma popcnttaxa de transferência de 4 por clock, então isso é provavelmente ideal, mas a família Bulldozer tem uma por popcnt r64,r64taxa de transferência de 4 relógios ... agner.org/optimize ).
Pedro Cordes

4
Eu mesmo estou usando uma versão intrínseca. No entanto, quando eu respondi, postei o que tinha e sabia em posts anteriores que, logo que escrevo para assembler, um esperto sempre indica que deveria ter feito isso de maneira intrínseca. Quando eu desenvolvo, escrevo assembler primeiro, depois, quando gosto do resultado, passo para intrínsecos. Esse sou eu. Acabei de postar minha resposta quando só tinha minha versão assembler 'test'.
Anders Cedronius

16

Essa é outra solução para quem gosta de recursão.

A ideia é simples. Divida a entrada pela metade e troque as duas metades, continue até atingir o bit único.

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

Aqui está uma função recursiva para resolvê-lo. (Observe que usei entradas não assinadas, para que funcione com entradas de tamanho até (int não assinado) * 8 bits.

A função recursiva usa 2 parâmetros - O valor cujos bits precisam ser revertidos e o número de bits no valor.

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

Esta é a saída:

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

Essa abordagem falha ao funcionar no exemplo de 24 bits (3º)? Não estou familiarizado com os operadores C e bit a bit, mas pela sua explicação da abordagem, acho que 24-> 12-> 6-> 3 (3 bits desiguais para dividir). Como numBitsé int, quando você divide 3 por 2 para a função param, ela será arredondada para 1?
Brennan

13

Bem, isso certamente não será uma resposta como a de Matt J, mas espero que ainda seja útil.

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

Essa é exatamente a mesma idéia que o melhor algoritmo de Matt, exceto que existe esta pequena instrução chamada BSWAP que troca os bytes (não os bits) de um número de 64 bits. Então b7, b6, b5, b4, b3, b2, b1, b0 se tornam b0, b1, b2, b3, b4, b5, b6, b7. Como estamos trabalhando com um número de 32 bits, precisamos mudar nosso número de bytes trocados para 32 bits. Isso nos deixa com a tarefa de trocar os 8 bits de cada byte que está pronto e pronto! Foram realizadas.

Tempo: na minha máquina, o algoritmo de Matt foi executado em ~ 0,52 segundos por teste. A mina funcionou em cerca de 0,42 segundos por teste. 20% mais rápido não é ruim, eu acho.

Se você está preocupado com a disponibilidade da instrução BSWAP, a Wikipedia lista a instrução BSWAP como adicionada ao 80846, lançada em 1989. Deve-se notar que a Wikipedia também afirma que essa instrução só funciona em registros de 32 bits, o que claramente não é o Na minha máquina, ele funciona muito bem apenas em registros de 64 bits.

Este método funcionará igualmente bem para qualquer tipo de dados integral, para que o método possa ser generalizado trivialmente, passando o número de bytes desejados:

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

que pode ser chamado como:

    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

O compilador deve ser capaz de otimizar o parâmetro extra (assumindo que o compilador alinha a função) e, para o sizeof(size_t)caso, o deslocamento à direita seria removido completamente. Observe que o GCC, pelo menos, não é capaz de remover o BSWAP e o deslocamento para a direita, se aprovado sizeof(char).


2
De acordo com o volume de referência 2A do conjunto de instruções da Intel ( intel.com/content/www/us/en/processors/… ), existem duas instruções BSWAP: BSWAP r32 (trabalhando em registradores de 32 bits), codificado como 0F C8 + rd e BSWAP r64 (trabalhando em registradores de 64 bits), codificado como REX.W + 0F C8 + rd.
Nubok 19/06

Você diz que pode ser usado assim: "n = reverse (n, sizeof (size_t)); // reverter 64 bits", no entanto, isso fornecerá apenas 32 bits de resultado, a menos que todas as constantes sejam estendidas para 64 bits, então funciona.
rajkosto

@rajkosto a partir do C ++ 11, os tipos permitidos de literais inteiros incluem o unsigned long long intque deve ser de pelo menos 64 bits, conforme aqui e aqui
#

OK? Só estou dizendo que, se você deseja que isso funcione nos valores de 64 bits, é necessário estender seus literais (para que sejam 0xf0f0f0f0f0f0f0f0ull, por exemplo), caso contrário, os 32 bits altos do resultado serão todos os 0s.
rajkosto

@rajkosto Ah, eu tinha entendido mal o seu primeiro comentário, eu ter corrigido isso agora
SirGuy

13

A resposta de Anders Cedronius fornece uma ótima solução para pessoas que têm uma CPU x86 com suporte para AVX2. Para plataformas x86 sem suporte para AVX ou plataformas não-x86, uma das seguintes implementações deve funcionar bem.

O primeiro código é uma variante do método clássico de particionamento binário, codificado para maximizar o uso do idioma shift-plus-logic útil em vários processadores ARM. Além disso, ele usa geração de máscara on-the-fly, o que pode ser benéfico para os processadores RISC que, caso contrário, exigem várias instruções para carregar cada valor de máscara de 32 bits. Compiladores para plataformas x86 devem usar propagação constante para calcular todas as máscaras no tempo de compilação, em vez do tempo de execução.

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

No volume 4A de "The Art of Computer Programming", D. Knuth mostra maneiras inteligentes de reverter bits que surpreendentemente requerem menos operações do que os algoritmos de particionamento binário clássicos. Um desses algoritmos para operandos de 32 bits, que não consigo encontrar no TAOCP, é mostrado neste documento no site do 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;
}

Usando o compilador Intel C / C ++ 13.1.3.198, as duas funções acima auto-vectorizam automaticamente os XMMregistros de segmentação agradável . Eles também podem ser vetorizados manualmente sem muito esforço.

No meu IvyBridge Xeon E3 1270v2, usando o código auto-vetorizado, 100 milhões de uint32_tpalavras foram invertidas em 0,070 segundos usando brev_classic()e 0,068 segundos usando brev_knuth(). Tomei o cuidado de garantir que minha referência não fosse limitada pela largura de banda da memória do sistema.


2
@JoelSnyder Suponho que por "muitos números mágicos" você está se referindo principalmente brev_knuth()? A atribuição no PDF da Hacker's Delight parece indicar que esses números são diretamente do próprio Knuth. Não posso afirmar que compreendi a descrição de Knuth dos princípios de design subjacentes no TAOCP suficientemente para explicar como as constantes foram derivadas, ou como se poderia abordar as constantes derivadas e os fatores de deslocamento para tamanhos de palavras arbitrários.
Njuffa

8

Supondo que você tenha uma matriz de bits, que tal: 1. A partir do MSB, insira os bits em uma pilha, um por um. 2. Coloque os bits desta pilha em outra matriz (ou a mesma matriz, se você quiser economizar espaço), colocando o primeiro bit exibido no MSB e passando para os bits menos significativos a partir daí.

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
Este me fez sorrir :) Eu adoraria ver uma referência desta solução C # contra um dos que descrevi acima, em otimizado C.
Matt J

LOL ... Mas ei! o adjetivo 'melhor' no 'melhor algoritmo' é uma coisa muito subjetiva: D
Frederick o tolo

7

A instrução nativa do ARM "rbit" pode fazer isso com 1 ciclo de CPU e 1 registro de CPU extra, impossível de bater.


6

Isso não é trabalho para um humano! ... mas perfeito para uma máquina

Estamos em 2015, 6 anos após a primeira pergunta. Os compiladores se tornaram nossos mestres e nosso trabalho como seres humanos é apenas ajudá-los. Então, qual é a melhor maneira de dar nossas intenções à máquina?

A reversão de bits é tão comum que você deve se perguntar por que o ISA cada vez maior do x86 não inclui uma instrução para fazê-lo de uma só vez.

O motivo: se você der sua verdadeira intenção concisa ao compilador, a reversão de bits deverá levar apenas ~ 20 ciclos de CPU . Deixe-me mostrar como criar reverse () e usá-lo:

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

A compilação deste programa de amostra com a versão Clang> = 3.6, -O3, -march = native (testada com Haswell) fornece código de qualidade de arte usando as novas instruções do AVX2, com um tempo de execução de 11 segundos processando ~ 1 bilhão de reversos () s. Isso é aproximadamente 10 ns por reverso (), com o ciclo de 0,5 ns da CPU assumindo que 2 GHz nos coloca nos 20 ciclos da CPU.

  • Você pode ajustar 10 reverse () s no tempo que leva para acessar a RAM uma vez para uma única matriz grande!
  • Você pode ajustar 1 reverso () no tempo necessário para acessar duas vezes a LUT do cache L2.

Advertência: esse código de amostra deve permanecer como uma referência decente por alguns anos, mas acabará por começar a mostrar sua idade, uma vez que os compiladores sejam inteligentes o suficiente para otimizar main () para imprimir apenas o resultado final, em vez de realmente calcular qualquer coisa. Mas, por enquanto, ele funciona em mostrar reverse ().


Bit-reversal is so common...Eu não sei disso. Trabalho com código que lida com dados no nível de bits praticamente todos os dias e não me lembro de ter tido essa necessidade específica. Em quais cenários você precisa? - Não que não seja um problema interessante de resolver por si só.
500 - Erro interno do servidor

@ 500-InternalServerError Acabo precisando dessa função muitas vezes na inferência gramatical com estruturas de dados rápidas e sucintas. Uma árvore binária normal codificada como um bitarray acaba inferindo a gramática na ordem "big endian". Mas, para melhor generalização, se você construir uma árvore (bititaray) com nós trocados pela permutação de reversão de bits, as seqüências de gramática aprendidas estão em "little endian". Essa alternância permite inferir cadeias de comprimento variável em vez de tamanhos inteiros fixos. Essa situação também aparece muito na FFT eficiente: consulte en.wikipedia.org/wiki/Bit-reversal_permutation

1
Obrigado, de alguma maneira, consegui intuir que a FFT pode estar envolvida na sua resposta :)
500 - Internal Server Error

por que apenas 20 ciclos? Qual arquitetura? Isso é verdade para todas as arquiteturas VLIW super amplas do futuro até a humanidade e nossas descidas desaparecerem? Perguntas apenas, sem respostas ... downvote para o inferno novamente
Quonux


5

Eu sei que não é C, mas asm:

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

Isso funciona com o bit de transporte, para que você também possa salvar sinalizadores


1
Eu acho que você poderia usar a palavra-chave asm , que seria bastante rápida.
Tom

Isso nem funciona. Eu acho que você deseja rclmudar CF para var1, em vez de apenas o shlque não lê sinalizadores. (Ou adc dx,dx) Mesmo com essa correção, isso é ridiculamente lento, usando as loopinstruções lentas e mantendo a var1memória! Na verdade, acho que isso deveria estar produzindo a saída no AX, mas salva / restaura o valor antigo do AX por cima do resultado.
Peter Cordes

4

Implementação com pouca memória e mais rápida.

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

Bem, isso é basicamente o mesmo que o primeiro "reverse ()", mas é de 64 bits e precisa apenas de uma máscara imediata para ser carregada do fluxo de instruções. O GCC cria código sem saltos, portanto isso deve ser bem rápido.

#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

Fiquei curioso em saber quão rápido seria a rotação bruta óbvia. Na minha máquina (i7 @ 2600), a média para 1.500.150.000 iterações era 27.28 ns(acima de um conjunto aleatório de 131.071 números inteiros de 64 bits).

Vantagens: a quantidade de memória necessária é pequena e o código é simples. Eu diria que também não é tão grande assim. O tempo necessário é previsível e constante para qualquer entrada (128 operações SHIFT aritméticas + 64 operações AND lógicas + 64 operações OR lógicas).

Comparei com o melhor tempo obtido por @Matt J - que tem a resposta aceita. Se eu li sua resposta corretamente, o melhor que ele obteve foram 0.631739segundos para as 1,000,000iterações, o que leva a uma média de 631 nspor rotação.

O snippet de código que usei é este abaixo:

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 Não sei se entendi sua pergunta.
Marian adam

obrigado por perceber o bug, eu corrigi o exemplo de código fornecido.
Marian adam

3

Você pode querer usar a biblioteca de modelos padrão. Pode ser mais lento que o código acima mencionado. No entanto, parece-me mais claro e fácil de entender.

 #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

Genérico

Código C. Usando dados de entrada de 1 byte num como exemplo.

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

A pergunta era "mais eficiente", não "simples / direta".
Peter Cordes

1

Que tal o seguinte:

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

Pequeno e fácil (apenas 32 bits).


A pergunta era "mais eficiente"; podemos descartar o loop 32 vezes. (E, especialmente, não mudando a máscara, bem como ter de mudar o resultado até o LSB)
Peter Cordes

1

Eu pensei que esta é uma das maneiras mais simples de reverter o bit. informe-me se houver alguma falha nessa lógica. basicamente nessa lógica, verificamos o valor do bit na posição. defina o bit se o valor for 1 na posição invertida.

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

A pergunta era "mais eficiente", não "simples / direta".
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;
}

Interessante, mas a divisão por uma variável de tempo de execução é lenta. ké sempre uma potência de 2, mas os compiladores provavelmente não provam isso e o transformam em bit-scan / shift.
Peter Cordes

0

Eu acho que o método mais simples que conheço segue. MSBé entrada e LSBsaída 'invertida':

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

Outra solução baseada em loop que sai rapidamente quando o número é baixo (em C ++ para vários tipos)

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

ou em C para um int não assinado

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

Parece que muitos outros posts estão preocupados com a velocidade (ou seja, melhor = mais rápido). E a simplicidade? Considerar:

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

e espero que o compilador inteligente otimize para você.

Se você deseja reverter uma lista mais longa de bits (contendo sizeof(char) * nbits), pode usar esta função para obter:

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

Isso reverteria [10000000, 10101010] para [01010101, 00000001].


Você tem três turnos no loop interno. Salve um com ith_bit = (c >> i) & 1. Salve também um SUB deslocando em reversed_charvez de mudar o bit, a menos que você espere que ele compile no x86 para sub something/ bts reg,regpara definir o enésimo bit no registro de destino.
Peter Cordes

-1

Reversão de bits em pseudo código

origem -> byte a ser revertido b00101100 destino -> revertido, também precisa ser do tipo não assinado, para que o bit de sinal não seja propagado para baixo

copiar para temp, para que o original não seja afetado, também precisa ser do tipo não assinado, para que o bit de sinal não seja deslocado automaticamente

bytecopy = b0010110

LOOP8: // faça este teste 8 vezes se a bytecopy for <0 (negativo)

    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

Minha solução simples

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
O que é i? Além disso, o que é essa constante mágica * 4? É isso CHAR_BIT / 2?
Peter Cordes

-1

Isso é para 32 bits, precisamos alterar o tamanho se considerarmos 8 bits.

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

Lendo o número inteiro de entrada "num" na ordem LSB-> MSB e armazenando em num_reverse na ordem MSB-> LSB.


1
Você deve adicionar uma explicação ao código para que ele seja entendido mais facilmente.
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
Geralmente, as respostas são muito mais úteis se incluem uma explicação sobre o que o código pretende fazer e por que isso resolve o problema.
IKavanagh #
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.