Algoritmo efficiente per l'inversione dei bit (da MSB-> LSB a LSB-> MSB) in C


243

Qual è l'algoritmo più efficiente per ottenere quanto segue:

0010 0000 => 0000 0100

La conversione è da MSB-> LSB a LSB-> MSB. Tutti i bit devono essere invertiti; cioè, questo non è scambio di endianness.


1
Penso che il nome appropriato sia un'operazione bit a bit.
Kredns,

5
Penso che volessi dire inversione, non rotazione.
Juliano,

2
La maggior parte dei processori ARM ha un funzionamento integrato per questo. ARM Cortex-M0 no, e ho scoperto che l'uso di una tabella per byte per scambiare i bit è l'approccio più veloce.
Starblue,

2
Vedi anche Bit Twiddling Hacks di Sean Eron Anderson .
jww

2
Per favore, definisci "migliore"
Lee Taylor,

Risposte:


497

NOTA : tutti gli algoritmi di seguito sono in C, ma dovrebbero essere portabili nella tua lingua preferita (non guardarmi quando non sono così veloci :)

Opzioni

Memoria insufficiente (macchina a 32 bit int, 32 bit) (da qui ):

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

}

Dalla famosa pagina Bit Twiddling Hacks :

Il più veloce (tabella di ricerca) :

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

Puoi estendere questa idea a 64 bit into scambiare memoria per la velocità (supponendo che la tua cache di dati L1 sia abbastanza grande) e invertire 16 bit alla volta con una tabella di ricerca con voce a 64 KB.


Altri

Semplice

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

Più veloce (processore a 32 bit)

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

Più veloce (processore a 64 bit)

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

Se vuoi farlo su un 32-bit int, basta invertire i bit in ciascun byte e invertire l'ordine dei byte. Questo è:

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

risultati

Ho confrontato le due soluzioni più promettenti, la tabella di ricerca e bit-AND (la prima). La macchina di prova è un laptop con 4 GB di DDR2-800 e un Core 2 Duo T7500 a 2,4 GHz, 4 MB di cache L2; YMMV. Ho usato gcc 4.3.2 su Linux a 64 bit. OpenMP (e i collegamenti GCC) sono stati utilizzati per i timer ad alta risoluzione.

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

Ho provato entrambi gli approcci con diverse ottimizzazioni, ho eseguito 3 prove per ogni livello e ogni prova ha invertito 100 milioni casuali unsigned ints. Per l'opzione della tabella di ricerca, ho provato entrambi gli schemi (opzioni 1 e 2) indicati nella pagina degli hack bit per bit. I risultati sono mostrati di seguito.

Bitwise 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

Tabella di ricerca (opzione 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  

Tabella di ricerca (opzione 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

Conclusione

Utilizzare la tabella di ricerca, con l'opzione 1 (l'indirizzamento dei byte è sorprendentemente lento) se sei preoccupato per le prestazioni. Se hai bisogno di spremere ogni ultimo byte di memoria dal tuo sistema (e potresti, se ti preoccupi delle prestazioni dell'inversione dei bit), anche le versioni ottimizzate dell'approccio bitwise-AND non sono troppo squallide.

Avvertimento

Sì, so che il codice di riferimento è un hack completo. Suggerimenti su come migliorarlo sono più che benvenuti. Cose che conosco:

  • Non ho accesso a ICC. Potrebbe essere più veloce (per favore, rispondi in un commento se puoi provarlo).
  • Una tabella di ricerca 64K può fare bene su alcune microarchitettura moderne con L1D di grandi dimensioni.
  • -mtune = nativo non ha funzionato per -O2 / -O3 (ld fatto saltare in aria con qualche errore di ridefinizione del simbolo pazzo), quindi non credo che il codice generato sia ottimizzato per la mia microarchitettura.
  • Potrebbe esserci un modo per farlo leggermente più velocemente con SSE. Non ho idea di come, ma con una replica veloce, un pacchetto bit per bit e istruzioni frizzanti, ci deve essere qualcosa lì.
  • Conosco un assemblaggio x86 sufficiente per essere pericoloso; ecco il codice GCC generato su -O3 per l'opzione 1, quindi qualcuno più esperto di me stesso può verificarlo:

32-bit

.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

EDIT: ho anche provato a utilizzare i uint64_ttipi sulla mia macchina per vedere se ci fosse un aumento delle prestazioni. Le prestazioni erano circa il 10% più veloci rispetto a 32 bit ed erano quasi identiche se si utilizzavano solo tipi a 64 bit per invertire bit su due inttipi a 32 bit alla volta o se si invertivano effettivamente bit a metà di 64- valori di bit. Di seguito è mostrato il codice assembly (per il primo caso, invertire i bit per due inttipi a 32 bit alla volta):

.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 per posta eccessivamente dettagliata e approfondita. j / k. +1.
Aprire il

8
È stato un esercizio interessante, se non del tutto soddisfacente. Se non altro, spero che vedere il processo sia costruttivo per qualcun altro che potrebbe voler confrontare qualcosa di più meritorio :)
Matt J

5
Mio Dio! Penso di aver trovato ... quello che potrebbe benissimo essere ... un VERO esemplare. Dovrò consultare i miei documenti e fare ulteriori ricerche, ma qualcosa mi dice (Dio, aiutami), che questa è di gran lunga la risposta più grande, più completa e utile che Stack Overflow abbia ancora. Perfino John Skeet sarebbe sia sconvolto che colpito!
zeboidlund,

3
Tieni presente che un particolare difetto del microbenchmarking (tra un elenco di molti altri) è che tende a favorire artificialmente le soluzioni basate sulla tabella di ricerca. Poiché il benchmark sta ripetendo l'unica operazione in un ciclo, spesso scoprirà che l'utilizzo di una tabella di ricerca che si adatta solo a L1 è il più veloce, perché tutto colpirà ogni volta in L1 poiché non esiste alcuna pressione cache. In un caso d'uso reale, l'operazione verrà di solito interlacciata con altre operazioni che causano una certa pressione nella cache. Una mancanza nella RAM potrebbe richiedere 10 o 100 volte più del solito, ma questo viene ignorato nei benchmark.
BeeOnRope,

2
Il risultato è che se due soluzioni sono vicine, spesso scelgo la soluzione non LUT (o quella con la LUT più piccola) perché l'impatto reale di una LUT può essere grave. Ancora meglio sarebbe confrontare ogni soluzione "in situ" - dove viene effettivamente utilizzata nell'applicazione più grande, con input realistici. Naturalmente, non sempre abbiamo tempo per questo e non sappiamo sempre quale input realistico sia.
BeeOnRope,

80

Questo thread ha attirato la mia attenzione poiché si occupa di un semplice problema che richiede molto lavoro (cicli della CPU) anche per una CPU moderna. E un giorno rimasi lì anche con lo stesso problema ¤ #% "#". Ho dovuto invertire milioni di byte. Tuttavia so che tutti i miei sistemi di destinazione sono basati su Intel moderna, quindi iniziamo a ottimizzare all'estremo !!!

Quindi ho usato il codice di ricerca di Matt J come base. il sistema su cui sto confrontando è un i7 haswell 4700eq.

Ricerca di Matt J che lancia bit 400.000.000 di byte: circa 0,272 secondi.

Sono quindi andato avanti e ho provato a vedere se il compilatore ISPC di Intel potesse vettorializzare l'aritmetica in reverse.c.

Non ti annoierò con le mie scoperte qui poiché ho cercato molto di aiutare il compilatore a trovare roba, comunque ho finito con prestazioni di circa 0,15 secondi per capovolgere 400.000.000 di byte. È una grande riduzione, ma per la mia applicazione è ancora troppo lento ..

Quindi le persone mi permettono di presentare il bitflipper basato su Intel più veloce al mondo. Orologio a:

Tempo di bitflip 400000000 byte: 0,050082 secondi !!!!!

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

Gli printf sono per il debug.

Ecco il cavallo di battaglia:

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

Il codice richiede 32 byte, quindi maschera gli stuzzichini. Il bocconcino alto viene spostato a destra di 4. Quindi uso vpshufb e ymm4 / ymm3 come tabelle di ricerca. Potrei usare una sola tabella di ricerca, ma poi dovrei spostare a sinistra prima di OR di nuovo insieme gli stuzzichini.

Ci sono modi ancora più veloci di lanciare i bit. Ma sono legato a thread singolo e CPU, quindi questo è stato il più veloce che ho potuto raggiungere. Puoi fare una versione più veloce?

Non fare commenti sull'uso dei comandi intrinsechi equivalenti del compilatore Intel C / C ++ ...


2
Ti meriti molto più voti di così. Sapevo che questo dovrebbe essere fattibile pshub, perché dopo tutto anche il miglior popcount ha finito! Lo avrei scritto qui se non fosse stato per te. Complimenti.
Iwillnotexist Idonotexist,

3
Grazie! 'popcnt' è un altro dei miei argomenti preferiti;) Dai un'occhiata alla mia versione BMI2: result = __ tzcnt_u64 (~ _pext_u64 (data [i], data [i]));
Anders Cedronius,

3
Nome del file asm: bitflip_asm.s quindi: yasm -f elf64 bitflip_asm.s Nome del file c: bitflip.c quindi: g ++ -fopenmp bitflip.c bitflip_asm.o -o bitflip Questo è tutto.
Anders Cedronius,

4
Le CPU Intel hanno le unità di esecuzione per popcnt, tzcnte pexttutte sulla porta 1. Quindi ogni pexto tzcntti costa un popcntthroughput. Se i tuoi dati sono caldi nella cache L1D, il modo più veloce per contare un array su CPU Intel è con AVX2 pshufb. (Ryzen ha un popcntthroughput di 4 per clock, quindi è probabilmente ottimale, ma la famiglia Bulldozer ha un popcnt r64,r64throughput di 4 per clock ... agner.org/optimize ).
Peter Cordes,

4
Sto usando me stesso una versione intrinseca. Tuttavia, quando ho risposto, ho pubblicato quello che avevo e sapevo dai post precedenti che non appena scrivo assembler un aleck intelligente sottolinea sempre che avrei dovuto farlo in modo intrinseco. Quando sviluppo scrivo prima l'assemblatore, poi, quando mi piace il risultato, mi muovo verso gli intrinseci.
Anders Cedronius,

16

Questa è un'altra soluzione per chi ama la ricorsione.

L'idea è semplice Dividi per metà l'input e scambia le due metà, continua finché non raggiunge il singolo bit.

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

Ecco una funzione ricorsiva per risolverlo. (Nota: ho usato interi senza segno, quindi può funzionare con input fino a sizeof (unsigned int) * 8 bit.

La funzione ricorsiva accetta 2 parametri: il valore i cui bit devono essere invertiti e il numero di bit nel valore.

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

Questo è l'output:

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

Questo approccio non funziona nell'esempio a 24 bit (3 °)? Non ho molta familiarità con gli operatori C e bit a bit, ma dalla tua spiegazione dell'approccio sto indovinando 24-> 12-> 6-> 3 (3 bit irregolari da dividere). Come numBitsè int, quando dividi 3 per 2 per la funzione param sarà arrotondato per difetto a 1?
Brennan,

13

Beh, questa sicuramente non sarà una risposta come quella di Matt J, ma spero che sia ancora utile.

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

Questa è esattamente la stessa idea del miglior algoritmo di Matt, tranne per il fatto che esiste questa piccola istruzione chiamata BSWAP che scambia i byte (non i bit) di un numero a 64 bit. Quindi b7, b6, b5, b4, b3, b2, b1, b0 diventa b0, b1, b2, b3, b4, b5, b6, b7. Poiché stiamo lavorando con un numero a 32 bit, dobbiamo spostare il nostro numero scambiato in byte di 32 bit. Questo ci lascia solo con il compito di scambiare gli 8 bit di ogni byte che è fatto e voilà! sono stati fatti.

Tempistica: sulla mia macchina, l'algoritmo di Matt ha funzionato in ~ 0,52 secondi per prova. Il mio ha funzionato in circa 0,42 secondi per prova. Il 20% più veloce non è male, credo.

Se sei preoccupato per la disponibilità dell'istruzione BSWAP Wikipedia elenca l'istruzione BSWAP come aggiunta con 80846 che è stata pubblicata nel 1989. Va notato che Wikipedia afferma anche che questa istruzione funziona solo su registri a 32 bit che non è chiaramente il caso sulla mia macchina, funziona molto solo su registri a 64 bit.

Questo metodo funzionerà ugualmente bene per qualsiasi tipo di dati integrale, quindi il metodo può essere generalizzato in modo banale passando il numero di byte desiderato:

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

che può quindi essere chiamato come:

    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

Il compilatore dovrebbe essere in grado di ottimizzare il parametro aggiuntivo (supponendo che il compilatore includa la funzione) e, nel sizeof(size_t)caso, lo spostamento a destra verrebbe rimosso completamente. Nota che almeno GCC non è in grado di rimuovere BSWAP e di spostare a destra se passato sizeof(char).


2
Secondo Intel Instruction Set Reference Volume 2A ( intel.com/content/www/us/en/processors/… ) ci sono due istruzioni BSWAP: BSWAP r32 (che lavora su registri a 32 bit), codificato come 0F C8 + rd e BSWAP r64 (funzionante su registri a 64 bit), codificato come REX.W + 0F C8 + rd.
Nubok,

Dici che può essere usato in questo modo: "n = reverse (n, sizeof (size_t)); // reverse 64 bit" tuttavia questo darà solo 32 bit di risultato a meno che tutte le costanti non siano estese a 64 bit, quindi funziona.
rajkosto,

@rajkosto a partire da C ++ 11 include i tipi consentiti di valori letterali interi unsigned long long intche devono essere almeno 64 bit, come qui e qui
SirGuy

Ok? Sto solo dicendo che se vuoi che funzioni su valori a 64 bit, devi estendere i tuoi valori letterali (quindi sono 0xf0f0f0f0f0f0f0f0ull, per esempio), altrimenti gli alti 32 bit del risultato saranno tutti 0.
rajkosto,

@rajkosto Ah, avevo frainteso il tuo primo commento, l'ho risolto ora
SirGuy

13

La risposta di Anders Cedronius fornisce un'ottima soluzione per le persone che hanno una CPU x86 con supporto AVX2. Per le piattaforme x86 senza supporto AVX o piattaforme non x86, una delle seguenti implementazioni dovrebbe funzionare bene.

Il primo codice è una variante del classico metodo di partizionamento binario, codificato per massimizzare l'uso del linguaggio shift-plus-logic utile su vari processori ARM. Inoltre, utilizza la generazione di maschere al volo che potrebbe essere utile per i processori RISC che altrimenti richiedono più istruzioni per caricare ciascun valore di maschera a 32 bit. I compilatori per piattaforme x86 devono utilizzare la propagazione costante per calcolare tutte le maschere in fase di compilazione anziché in fase di esecuzione.

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

Nel volume 4A di "The Art of Computer Programming", D. Knuth mostra modi intelligenti di invertire bit che in qualche modo sorprendentemente richiedono meno operazioni rispetto ai classici algoritmi di partizionamento binario. Uno di questi algoritmi per operandi a 32 bit, che non riesco a trovare in TAOCP, è mostrato in questo documento sul sito Web di 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;
}

Utilizzando il compilatore C / C ++ del compilatore Intel 13.1.3.198, entrambe le funzioni di cui sopra si auto-vettorizzano automaticamente i XMMregistri di destinazione. Potrebbero anche essere vettorializzati manualmente senza molto sforzo.

Sul mio IvyBridge Xeon E3 1270v2, usando il codice auto-vettoriale, 100 milioni di uint32_tparole sono state invertite in bit in 0,070 secondi usando brev_classic()e 0,068 secondi usando brev_knuth(). Mi sono preso cura di garantire che il mio benchmark non fosse limitato dalla larghezza di banda della memoria di sistema.


2
@JoelSnyder Presumo per "molti numeri magici" a cui ti riferisci principalmente brev_knuth()? L'attribuzione nel PDF di Hacker's Delight sembra indicare che questi numeri provengono direttamente dallo stesso Knuth. Non posso affermare di aver compreso sufficientemente la descrizione di Knuth dei principi di progettazione sottostanti in TAOCP per spiegare come sono state derivate le costanti o come si andrebbero le costanti derivate e i fattori di spostamento per dimensioni di parole arbitrarie.
njuffa,

8

Supponendo di avere una serie di bit, che ne dici di questo: 1. Partendo da MSB, spingere i bit in uno stack uno per uno. 2. Pop bit da questo stack in un altro array (o lo stesso array se si desidera risparmiare spazio), posizionando il primo bit scoppiato in MSB e passando a bit meno significativi da lì.

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
Questo mi ha fatto sorridere :) Mi piacerebbe vedere un punto di riferimento di questa soluzione C # contro uno di quelli che ho delineato sopra in ottimizzato C.
Matt J

LOL ... Ma hey! l'aggettivo "migliore" nel "miglior algoritmo" è una cosa piuttosto soggettiva: D
Frederick The Fool

7

L'istruzione ARM nativa "rbit" può farlo con 1 ciclo di CPU e 1 registro di CPU extra, impossibile da battere.


6

Questo non è un lavoro per un essere umano! ... ma perfetto per una macchina

Questo è il 2015, a 6 anni dalla prima domanda. Da allora i compilatori sono diventati i nostri padroni e il nostro lavoro di umani è solo quello di aiutarli. Quindi qual è il modo migliore per dare le nostre intenzioni alla macchina?

L'inversione dei bit è così comune che devi chiederti perché l'ISA in costante crescita di x86 non include un'istruzione per farlo una volta sola.

Il motivo: se dai il tuo vero intento conciso al compilatore, l'inversione dei bit dovrebbe richiedere solo ~ 20 cicli di CPU . Lascia che ti mostri come creare reverse () e usarlo:

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

Compilando questo programma di esempio con la versione Clang> = 3.6, -O3, -march = native (testato con Haswell), fornisce un codice di qualità grafica usando le nuove istruzioni AVX2, con un tempo di esecuzione di 11 secondi che elabora ~ 1 miliardo di reverse () s. Sono ~ 10 ns per reverse (), con un ciclo della CPU di .5 ns che assume 2 GHz ci mette ai dolci 20 cicli della CPU.

  • Puoi inserire 10 reverse () s nel tempo necessario per accedere alla RAM una volta per un singolo array di grandi dimensioni!
  • Puoi inserire 1 reverse () nel tempo necessario per accedere a una LUT cache L2 due volte.

Avvertenza: questo codice di esempio dovrebbe essere considerato un benchmark decente per alcuni anni, ma alla fine inizierà a mostrare la sua età una volta che i compilatori saranno abbastanza intelligenti da ottimizzare main () per stampare solo il risultato finale invece di calcolare realmente qualsiasi cosa. Ma per ora funziona in mostra reverse ().


Bit-reversal is so common...Non lo so. Lavoro con codice che si occupa di dati a livello di bit praticamente ogni giorno e non ricordo di aver mai avuto questa specifica esigenza. In quali scenari ne hai bisogno? - Non che non sia un problema interessante da risolvere a sé stante.
500 - Errore interno del server il

@ 500-InternalServerError Alla fine ho bisogno di questa funzione molte volte in inferenza grammaticale con strutture dati rapide e concise. Un normale albero binario codificato come bitarray finisce per dedurre la grammatica in ordine "big endian". Ma per una migliore generalizzazione se si costruisce un albero (bitarray) con nodi scambiati dalla permutazione di inversione di bit, le stringhe della grammatica appresa sono in "little endian". Tale commutazione consente di inferire stringhe a lunghezza variabile anziché dimensioni intere fisse. Questa situazione si presenta molto anche in FFT efficiente: vedi en.wikipedia.org/wiki/Bit-reversal_permutation

1
Grazie, in qualche modo sono riuscito a intuire che FFT potrebbe essere coinvolto nella tua risposta :)
500 - Errore interno del server il

perché solo 20 cicli? Quale architettura? È vero per tutte le super vaste architetture VLIW del futuro fino all'estinzione dell'umanità e delle nostre discese? Solo domande, nessuna risposta ... di nuovo downvote all'inferno
Quonux,


5

So che non è C ma asm:

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

Funziona con il bit carry, quindi puoi anche salvare flag


1
Immagino che potresti usare la parola chiave asm , che sarebbe abbastanza veloce.
Tom,

Questo non funziona nemmeno. Penso che tu voglia rclspostare CF in var1, invece shlche semplicemente non legge le bandiere. (O adc dx,dx). Anche con quella correzione, questo è ridicolmente lento, usando le loopistruzioni lente e tenendolo var1in memoria! In realtà penso che questo dovrebbe produrre l'output in AX, ma salva / ripristina il vecchio valore di AX oltre il risultato.
Peter Cordes,

4

Implementazione con memoria insufficiente e veloce.

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

Bene, questo è fondamentalmente lo stesso del primo "reverse ()" ma è a 64 bit e necessita solo di una maschera immediata per essere caricata dal flusso di istruzioni. GCC crea codice senza salti, quindi dovrebbe essere abbastanza veloce.

#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

Ero curioso di sapere quanto velocemente sarebbe stata l'ovvia rotazione grezza. Sulla mia macchina (i7 @ 2600), la media per 1.500.150.000 iterazioni era27.28 ns (su un set casuale di 131.071 numeri interi a 64 bit).

Vantaggi: la quantità di memoria necessaria è piccola e il codice è semplice. Direi che non è neanche così grande. Il tempo richiesto è prevedibile e costante per qualsiasi input (128 operazioni SHIFT aritmetiche + 64 operazioni AND logiche + 64 operazioni OR logiche).

Ho confrontato con il miglior tempo ottenuto da @Matt J - che ha la risposta accettata. Se leggo correttamente la sua risposta, il migliore che ha ottenuto sono stati i 0.631739secondi per le 1,000,000iterazioni, il che porta a una media di 631 nsper rotazione.

Lo snippet di codice che ho usato è questo qui sotto:

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 Non sono sicuro di aver capito la tua domanda.
marian adam,

grazie per aver notato il bug, ho corretto l'esempio di codice fornito.
marian adam,

3

Potresti voler utilizzare la libreria di modelli standard. Potrebbe essere più lento del codice sopra menzionato. Tuttavia, mi sembra più chiaro e più facile da capire.

 #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

Generico

Codice C. Utilizzando i dati di input a 1 byte num come esempio.

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

La domanda era "più efficiente", non "semplice / diretta".
Peter Cordes,

1

Che ne dici di quanto segue:

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

Piccolo e facile (anche se solo a 32 bit).


La domanda posta "più efficiente"; possiamo escludere il looping 32 volte. (E soprattutto non spostare la maschera e non dover spostare il risultato sull'LSB)
Peter Cordes,

1

Ho pensato che questo fosse uno dei modi più semplici per invertire il bit. per favore fatemi sapere se c'è qualche difetto in questa logica. sostanzialmente in questa logica, controlliamo il valore del bit in posizione. impostare il bit se il valore è 1 su posizione inversa.

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

La domanda era "più efficiente", non "semplice / diretta".
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, ma la divisione per una variabile di runtime è lenta. kè sempre una potenza di 2, ma i compilatori probabilmente non lo dimostreranno e lo trasformeranno in bit-scan / shift.
Peter Cordes,

0

Penso che segua il metodo più semplice che conosco. MSBè input ed LSBè output "invertito":

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

Un'altra soluzione basata su loop che esce rapidamente quando il numero è basso (in C ++ per più tipi)

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

o in C per un int

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

Sembra che molti altri post siano preoccupati per la velocità (ovvero migliore = più veloce). E la semplicità? Tener conto di:

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 spero che il compilatore intelligente si ottimizzi per te.

Se si desidera invertire un elenco più lungo di bit (contenenti sizeof(char) * nbit), è possibile utilizzare questa funzione per ottenere:

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

Ciò invertirebbe [10000000, 10101010] in [01010101, 00000001].


Hai 3 turni nel circuito interno. Salvane uno con ith_bit = (c >> i) & 1. Salvare anche un SUB spostando reversed_charinvece di spostare il bit, a meno che non si spera che si compili su x86 su sub something/ bts reg,regper impostare l'ennesimo bit nel registro di destinazione.
Peter Cordes,

-1

Inversione bit in pseudo codice

source -> byte da invertire b00101100 destinazione -> invertito, deve anche essere di tipo senza segno, quindi il bit di segno non viene propagato

copia in temp in modo che l'originale non sia interessato, inoltre deve essere di tipo senza segno in modo che il bit di segno non venga spostato automaticamente

bytecopy = b0010110

LOOP8: // fai questo test 8 volte se bytecopy è <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

La mia semplice soluzione

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
Cosa i? Inoltre, cos'è quella costante magica * 4? È vero CHAR_BIT / 2?
Peter Cordes,

-1

Questo è per 32 bit, dobbiamo cambiare la dimensione se consideriamo 8 bit.

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

Lettura del numero intero di input "num" nell'ordine LSB-> MSB e memorizzazione in num_reverse nell'ordine MSB-> LSB.


1
È necessario aggiungere una spiegazione al codice in modo che sia compreso più 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
In generale, le risposte sono molto più utili se includono una spiegazione di ciò che il codice è destinato a fare e perché risolve il problema.
IKavanagh,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.