254 votes

Algorithme efficace pour l'inversion de bits (de MSB->LSB à LSB->MSB) en C

Quel est l'algorithme le plus efficace pour réaliser ce qui suit :

0010 0000 => 0000 0100

La conversion se fait de MSB->LSB à LSB->MSB. Tous les bits doivent être inversés, c'est-à-dire que c'est no l'échange d'endianness.

1 votes

Je pense que le nom approprié est une opération de type bitwise.

5 votes

Je pense que tu voulais dire inversion, pas rotation.

2 votes

La plupart des processeurs ARM ont une opération intégrée pour cela. L'ARM Cortex-M0 ne le fait pas, et j'ai trouvé que l'utilisation d'une table par octet pour échanger les bits est l'approche la plus rapide.

510voto

Matt J Points 15475

NOTE : Tous les algorithmes ci-dessous sont en C, mais devraient être portables dans le langage de votre choix (ne me regardez pas quand ils sont moins rapides :).

Options

Mémoire basse (32 bits) int (machine 32 bits) (de aquí ):

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

}

Du célèbre Page "Bit Twiddling Hacks :

Le plus rapide (table de consultation) :

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

Vous pouvez étendre cette idée au 64 bits int ou échanger la mémoire contre la vitesse (en supposant que votre cache de données L1 est suffisamment grand), et inverser 16 bits à la fois avec une table de consultation de 64K entrées.


Autres

Simple

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

Plus rapide (processeur 32 bits)

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

Plus rapide (processeur 64 bits)

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

Si vous voulez faire cela sur un ordinateur 32-bit int il suffit d'inverser les bits de chaque octet, et d'inverser l'ordre des octets. C'est-à-dire :

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

Résultats

J'ai testé les deux solutions les plus prometteuses, la table de consultation et le bitwise-AND (la première). La machine de test est un ordinateur portable avec 4GB de DDR2-800 et un Core 2 Duo T7500 @ 2.4GHz, 4MB L2 Cache ; YMMV. J'ai utilisé gcc 4.3.2 sur Linux 64 bits. OpenMP (et les bindings GCC) ont été utilisés pour les timers à haute résolution.

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

J'ai essayé les deux approches avec plusieurs optimisations différentes, j'ai effectué 3 essais à chaque niveau, et chaque essai a inversé 100 millions d'échantillons aléatoires. unsigned ints . Pour l'option de la table de consultation, j'ai essayé les deux schémas (options 1 et 2) donnés sur la page des hacks bitwise. Les résultats sont montrés ci-dessous.

ET par 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

Table de recherche (option 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  

Table de recherche (option 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

Conclusion

Utiliser la table de consultation, avec l'option 1 (l'adressage par octet est sans surprise très lent) si vous êtes préoccupé par les performances. Si vous avez besoin d'exploiter jusqu'au dernier octet de mémoire de votre système (et c'est le cas, si vous vous souciez des performances de l'inversion de bit), les versions optimisées de l'approche bitwise-AND ne sont pas trop mal non plus.

Caveat

Oui, je sais que le code de référence est complètement bidouillé. Les suggestions sur la façon de l'améliorer sont plus que bienvenues. Les choses que je connais :

  • Je n'ai pas accès à l'ICC. Cela peut être plus rapide (veuillez répondre dans un commentaire si vous pouvez tester cela).
  • Une table de consultation de 64K peut faire l'affaire sur certaines microarchitectures modernes avec de grands L1D.
  • -mtune=native n'a pas fonctionné pour -O2/-O3 ( ld a explosé avec une erreur de redéfinition de symbole), donc je ne pense pas que le code généré soit adapté à ma microarchitecture.
  • Il y a peut-être un moyen de faire cela un peu plus rapidement avec SSE. Je n'ai aucune idée de comment, mais avec une réplication rapide, des instructions ET par bit emballées et des instructions swizzling, il doit y avoir quelque chose.
  • Je ne connais que suffisamment l'assemblage x86 pour être dangereux ; voici le code que GCC a généré sur -O3 pour l'option 1, afin que quelqu'un de plus compétent que moi puisse le vérifier :

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

EDIT : J'ai également essayé d'utiliser uint64_t sur mon ordinateur pour voir si les performances étaient améliorées. Les performances étaient environ 10 % plus rapides qu'en 32 bits, et étaient presque identiques, que vous utilisiez simplement des types 64 bits pour inverser les bits sur deux fichiers 32 bits. int à la fois, ou si vous inversez réellement les bits dans la moitié des valeurs de 64 bits. Le code assembleur est présenté ci-dessous (dans le premier cas, l'inversion des bits pour deux valeurs de 32 bits et de 64 bits). int à la fois) :

.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

0 votes

Je pense que la table 8 bits devrait être la plus rapide sur la plupart des architectures, mais seuls les benchmarks nous le diront.

0 votes

Vous avez raison ! Voir les résultats ci-dessus (dans une minute) :)

2 votes

-1 pour le poste excessivement détaillé et approfondi. j/k. +1.

88voto

Ce fil de discussion a attiré mon attention car il traite d'un problème simple qui nécessite beaucoup de travail (cycles CPU), même pour un CPU moderne. Un jour, je me suis aussi retrouvé avec le même problème de ¤#%"#". Je devais retourner des millions d'octets. Cependant, je sais que tous mes systèmes cibles sont des systèmes modernes à base d'Intel, alors commençons à optimiser à l'extrême ! !!

J'ai donc utilisé le code de recherche de Matt J comme base. Le système sur lequel j'effectue l'évaluation est un i7 haswell 4700eq.

Le lookup de Matt J a fait un bitflipping de 400 000 000 d'octets : Environ 0,272 secondes.

J'ai ensuite essayé de voir si le compilateur ISPC d'Intel pouvait vectoriser l'arithmétique dans le reverse.c.

Je ne vais pas vous ennuyer avec mes résultats ici puisque j'ai beaucoup essayé d'aider le compilateur à trouver des choses, de toute façon j'ai fini par obtenir des performances d'environ 0,15 secondes pour bitflip 400 000 000 octets. C'est une grande réduction mais pour mon application c'est encore beaucoup trop lent

Alors les gens, laissez-moi vous présenter le bitflipper basé sur Intel le plus rapide du monde. Cadencé à :

Temps pour bitflip 400000000 bytes : 0.050082 secondes ! !!!!

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

Les printf's sont pour le débogage

Voici la bête de somme :

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

Le code prend 32 octets puis masque les fragments. Le bit de poids fort est décalé de 4 vers la droite. J'utilise ensuite vpshufb et ymm4 / ymm3 comme tables de consultation. Je pourrais utiliser une seule table de consultation, mais je devrais alors décaler vers la gauche avant d'effectuer une nouvelle opération d'OU entre les quartets.

Il existe des moyens encore plus rapides de retourner les bits. Mais comme je suis limité à un seul fil et à un seul processeur, c'était le plus rapide que je pouvais réaliser. Pouvez-vous faire une version plus rapide ?

Veuillez ne faire aucun commentaire sur l'utilisation des commandes équivalentes intrinsèques du compilateur Intel C/C++...

2 votes

Tu mérites beaucoup plus de votes positifs que ça. Je savais que ça devait être faisable avec pshub parce qu'après tout, le meilleur popcount se fait aussi avec lui ! Je l'aurais écrit ici si ce n'était pas pour vous. Félicitations.

3 votes

Merci ! Le 'popcnt' est un autre de mes sujets favoris ;) Regardez ma version BMI2 : result=__tzcnt_u64(~_pext_u64(data[i],data[i])) ;

2 votes

Hah ! Pour celui-là, j'ai indépendamment compris quelque chose de similaire, parce que j'ai remarqué que pext(x, x) rassemble tous les uns (discontigus) consécutivement dans les quelques bits du bas, et pourrait donc être fait comme suit return 64-lzcnt(pext(x,x)) . Certes, return tzcnt(~pext(x,x)) est plus soigné et plus court.

17voto

Dennis Mathews Points 5261

C'est une autre solution pour les gens qui aiment la récursivité.

L'idée est simple. Diviser entrée par la moitié et de permuter les deux moitiés, continuer jusqu'à ce qu'il atteigne un seul 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

Voici une fonction récursive pour le résoudre. (Notez que j'ai utilisé unsigned ints, donc il peut travailler pour les entrées jusqu'à sizeof(unsigned int)*8 bits.

La fonction récursive prend 2 paramètres: La valeur dont les bits doivent pour être inversée et le nombre de bits de la valeur.

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

C'est la sortie:

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

1 votes

Cette approche ne fonctionne-t-elle pas sur l'exemple 24 bits (3e) ? Je ne suis pas très familier avec le C et les opérateurs bit à bit, mais d'après votre explication de l'approche, je suppose que 24->12->6->3 (3 bits inégaux à séparer). Comme numBits est int, lorsque vous divisez 3 par 2 pour la fonction param, il sera arrondi à 1 ?

17voto

GuyGreer Points 4240

Ce ne sera certainement pas une réponse comme celle de Matt J, mais j'espère qu'elle sera quand même 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;
}

C'est exactement la même idée que le meilleur algorithme de Matt, sauf qu'il existe une petite instruction appelée BSWAP qui échange les octets (et non les bits) d'un nombre de 64 bits. Donc b7,b6,b5,b4,b3,b2,b1,b0 devient b0,b1,b2,b3,b4,b5,b6,b7. Comme nous travaillons avec un nombre de 32 bits, nous devons décaler notre nombre échangé d'octets de 32 bits. Il ne nous reste plus qu'à échanger les 8 bits de chaque octet, ce qui est fait, et voilà, c'est fait.

Temps : sur ma machine, l'algorithme de Matt a fonctionné en ~0,52 seconde par essai. Le mien a fonctionné en environ 0,42 secondes par essai. 20% plus rapide, ce n'est pas mal, je pense.

Si vous êtes inquiet de la disponibilité de l'instruction BSWAP Wikipedia indique que l'instruction BSWAP a été ajoutée avec la 80846 qui est sortie en 1989. Il est à noter que Wikipedia indique également que cette instruction ne fonctionne que sur les registres 32 bits, ce qui n'est clairement pas le cas sur ma machine, elle ne fonctionne que sur les registres 64 bits.

Cette méthode fonctionnera également pour n'importe quel type de données intégrales. La méthode peut donc être généralisée de manière triviale en passant le nombre d'octets souhaité :

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

qui peut alors être appelé comme :

    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

Le compilateur devrait être en mesure d'optimiser l'élimination du paramètre supplémentaire (en supposant que le compilateur inline la fonction) et pour la fonction sizeof(size_t) Dans ce cas, le décalage vers la droite serait complètement supprimé. Notez que GCC, au moins, n'est pas capable de supprimer le BSWAP et le décalage à droite si on lui passe la commande sizeof(char) .

2 votes

Selon l'Intel Instruction Set Reference Volume 2A ( intel.com/content/www/us/en/processeurs/ ), il existe deux instructions BSWAP : BSWAP r32 (travail sur des registres 32 bits), qui est codée comme 0F C8+rd et BSWAP r64 (travail sur des registres 64 bits), qui est codée comme REX.W + 0F C8+rd.

0 votes

Vous dites que ça peut être utilisé comme ça : "n = reverse(n, sizeof(size_t));//reverse 64 bits" cependant cela ne donnera que 32bits de résultat à moins que toutes les constantes soient étendues à 64bits, alors cela fonctionne.

0 votes

@rajkosto En C++11, les types de littéraux entiers autorisés sont les suivants unsigned long long int qui doit être d'au moins 64 bits, conformément aux dispositions de l'article 3.1.1. aquí y aquí

8voto

Frederick The Fool Points 9092

En supposant que vous ayez un tableau de bits, que diriez-vous de ceci ? 1. En commençant par le MSB, pousser les bits dans une pile un par un. 2. Insérez les bits de cette pile dans un autre tableau (ou dans le même tableau si vous voulez économiser de l'espace), en plaçant le premier bit inséré dans le MSB et en allant vers les bits moins significatifs à partir de 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();
}

4 votes

Celui-ci m'a fait sourire :) J'aimerais bien voir un benchmark de cette solution C# par rapport à l'une de celles que j'ai décrites ci-dessus en C optimisé.

0 votes

LOL... Mais bon ! l'adjectif 'meilleur' dans le 'meilleur algorithme' est une chose assez subjective :D

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X