Ефективний алгоритм розвороту бітів (від MSB-> LSB до LSB-> MSB) у C


243

Який найбільш ефективний алгоритм для досягнення наступного:

0010 0000 => 0000 0100

Перетворення відбувається з MSB-> LSB до LSB-> MSB. Усі біти повинні бути перевернуті; тобто це не обміняння на витримки.


1
Я думаю, що відповідна назва - це побітна операція.
Креднс

5
Я думаю, ви мали на увазі перевертання, а не обертання.
Джуліано

2
Більшість процесорів ARM мають для цього вбудовану операцію. ARM Cortex-M0 не робить, і я виявив, що за допомогою байтової таблиці для обміну бітами - це найшвидший підхід.
starblue

2
Також дивіться біт Шойда Шойна Ерона Андерсона .
jww

2
Будь ласка, визначте "найкраще"
Лі Тейлор

Відповіді:


497

ПРИМІТКА : Усі алгоритми, наведені нижче, знаходяться на мові C, але повинні бути портативними на вашу обрану мову (просто не дивіться на мене, коли вони не такі швидкі :)

Параметри

Мало пам'яті (32-розрядна int, 32-розрядна машина) ( звідси ):

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

}

З відомої сторінки Bit Twiddling Hacks :

Найшвидший (таблиця пошуку) :

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

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

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

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

Ви можете розширити цю ідею на 64-бітну систему intабо торгувати пам’яттю на швидкість (якщо припустити, що кеш даних L1 досить великий) і одночасно повернути 16 біт за допомогою таблиці пошуку 64K-запису.


Інші

Простий

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

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

Швидше (32-бітний процесор)

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

Швидше (64-бітний процесор)

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

Якщо ви хочете зробити це на 32-бітному intпросто, переверніть біти в кожному байті і перейміть порядок байтів. Це є:

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

Результати

Я орієнтував два найперспективніші рішення, таблицю пошуку та розрядну-І (перше). Тестова машина - це ноутбук з 4 Гб DDR2-800 та Core 2 Duo T7500 при 2,4 ГГц, 4 МБ кеш-пам'яті L2; YMMV. Я використав gcc 4.3.2 у 64-розрядному Linux. OpenMP (та прив'язки GCC) використовувались для таймерів високої роздільної здатності.

реверс.c

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

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

}

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

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

    free(ints);
    free(ints2);

    return 0;
}

reverse_lookup.c

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

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

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

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

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

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

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

    free(ints);
    free(ints2);

    return 0;
}

Я спробував обидва підходи в декількох різних оптимізаціях, провів 3 випробування на кожному рівні, і кожне випробування перевернуло 100 мільйонів випадкових випадків unsigned ints. Для параметра таблиці пошуку я спробував обидві схеми (варіанти 1 та 2), наведені на сторінці побітових злому. Результати наведені нижче.

Побітові І

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

Таблиця пошуку (варіант 1)

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

Таблиця пошуку (варіант 2)

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

Висновок

Скористайтеся таблицею пошуку з варіантом 1 (байт-адресація не дивно повільна), якщо вас турбує продуктивність. Якщо вам потрібно видавити з вашої системи кожен останній байт пам'яті (і, можливо, якщо ви піклуєтесь про ефективність біт-реверсу), оптимізовані версії розрядного підходу також не надто хиткі.

Caveat

Так, я знаю, що базовий код - це повний злом. Пропозиції щодо її покращення є більш ніж вітаються. Що я знаю про:

  • Я не маю доступу до ICC. Це може бути швидше (будь ласка, відповідайте в коментарі, якщо ви можете перевірити це).
  • Таблиця пошуку 64K може спрацювати в деяких сучасних мікроархітектурах з великим L1D.
  • -mtune = native не працював для -O2 / -O3 ( ldпідірвався помилковою помилкою переосмислення символу), тому я не вірю, що створений код налаштований на мою мікроархітектуру.
  • Можливо, є спосіб зробити це трохи швидше за допомогою SSE. Я не маю поняття, як, але зі швидкою реплікацією, упакованою побіжно AND і шипучими інструкціями, там щось має бути.
  • Я знаю лише достатньо складання x86, щоб бути небезпечним; ось код GCC, сформований на -O3 для варіанту 1, тому хтось більш обізнаний, ніж я, може перевірити це:

32-розрядні

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

EDIT: Я також намагався використовувати uint64_tтипи на моїй машині, щоб побачити, чи є підвищення продуктивності. Продуктивність була приблизно на 10% швидшою, ніж 32-розрядна, і була майже однаковою, чи ви просто використовували 64-бітні типи для зворотного intперетворення бітів на два 32-бітні типи одночасно, чи ви насправді ревертували біти вдвічі більше 64- бітові значення. Код складання показаний нижче (для колишнього випадку, обернення бітів для двох 32-бітних intтипів одночасно):

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

2
-1 за надмірно детальну та ретельну посаду. j / k. +1.
жовтня

8
Це була цікава вправа, якщо не все, що виконує. Якщо нічого іншого, я сподіваюся побачити процес конструктивним для когось іншого, хто, можливо, захоче орієнтувати щось більш корисне :)
Matt J

5
Боже! Я думаю, що я знайшов ... що може бути дуже правдивим зразком. Мені доведеться проконсультуватися зі своїми документами та зробити подальші дослідження, але щось мені скаже (Боже, допоможи мені), що це, безумовно, найбільша, найбільш ретельна і корисна відповідь, яку Стек Overflow ще мав. Навіть Джон Скіт був би здивований і вражений!
zeboidlund

3
Майте на увазі, що однією особливою вадою мікробізового маркетингу (серед переліку багатьох інших) є те, що він, як правило, штучно сприяє пошуку рішень на основі таблиці. Оскільки тест повторює одну операцію в циклі, часто виявиться, що використання таблиці пошуку, яка просто вписується в L1, є найшвидшим, тому що все буде потрапляти в L1 кожного разу, оскільки тиску кешу взагалі немає. У реальному випадку використання операція зазвичай переплітається з іншими операціями, які викликають певний тиск кешу. Пропуск оперативної пам’яті може зайняти в 10 або 100 разів довше, ніж зазвичай, але це не враховується у орієнтирах.
BeeOnRope

2
Підсумок полягає в тому, що якщо два рішення близькі, я часто вибираю не-LUT рішення (або те, що має менший LUT), оскільки реальний вплив на LUT може бути серйозним. Ще краще було б орієнтувати кожне рішення "in situ" - там, де воно фактично використовується у більшій програмі, з реалістичним вкладом. Звичайно, у нас не завжди є на це час, і ми не завжди знаємо, що таке реалістичний внесок.
BeeOnRope

80

Цей потік привернув мою увагу, оскільки він стосується простої проблеми, яка вимагає багато роботи (цикли процесора) навіть для сучасного процесора. І одного разу я теж стояв там із тією ж проблемою ¤ #% "#". Мені довелося перевернути мільйони байтів. Однак я знаю, що всі мої цільові системи сучасні на базі Intel, тому почнемо оптимізувати до кінця !!!

Тому я використав код пошуку Метта Дж. система, на яку я орієнтую, - це i7 haswell 4700eq.

Бітпліп для пошуку Метта Дж 400 000 000 байт: приблизно 0,272 секунди.

Потім я пішов уперед і спробував перевірити, чи зможе компілятор ISPC від Intel векторизувати арифметику в зворотному рядку.

Я не збираюся набридати вам своїми висновками тут, оскільки я багато намагався допомогти компілятору знайти речі, інакше я закінчився продуктивністю близько 0,15 секунд, щоб перевернути 400 000 000 байт. Це велике зменшення, але для мого застосування це все ще дуже повільно.

Тож люди дозволяють мені представити найшвидший бітфліппер на базі Intel. Годинник:

Час на перевертання 400000000 байт: 0,050082 секунди !!!!!

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

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

using namespace std;

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

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

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

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

int main()
{

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

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

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

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

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

    // return with no errors
    return 0;
}

Printf's для налагодження ..

Ось робочий коник:

bits 64
global bitflipbyte

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

Код займає 32 байти, а потім маскує мітки. Високий кусок зміщується вправо на 4. Потім я використовую vpshufb та ymm4 / ymm3 як таблиці пошуку. Я міг би використовувати єдину таблицю пошуку, але тоді мені доведеться знову перемістити ліворуч, перш ніж ORing зблизити.

Існують ще швидші способи перевернути біти. Але я пов'язаний з одним потоком і процесором, тому це було найшвидшим, що я міг досягти. Чи можете ви зробити більш швидку версію?

Не пишіть коментарів щодо використання команд Intel C / C ++ Compiler Intrinsic Equivalent ...


2
Ви заслуговуєте на FAR більше грошей, ніж це. Я знав, що з цим слід займатися pshub, адже зрештою найкращий попконт теж робиться з ним! Я б написав це тут, якби не ви. Кудос.
Iwillnotexist Idonotexist

3
Дякую! 'popcnt' - ще одна моя улюблена тема;) Перевірте мою версію BMI2: результат = __ tzcnt_u64 (~ _pext_u64 (дані [i], дані [i]));
Андер Седроній

3
Назвіть файл asm: bitflip_asm.s тоді: yasm -f elf64 bitflip_asm.s Назвіть файл c: bitflip.c, тоді: g ++ -fopenmp bitflip.c bitflip_asm.o -o bitflip Ось це.
Андер Седроній

4
Процесори Intel мають одиниці виконання для popcnt, tzcntі pextвсі на порту 1. Отже, кожен pextабо tzcntкоштує вам popcntпропускної здатності. Якщо ваші дані гарячі в кеші L1D, найшвидший спосіб вискочити масив на процесорах Intel - за допомогою AVX2 pshufb. (Ryzen має 4 на тактову popcntпропускну здатність, тому це, мабуть, оптимально, але сім'я Bulldozer має один на 4 тактових частотиpopcnt r64,r64 ... agner.org/optimize ).
Пітер Кордес

4
Я сам використовую версію внутрішньої роботи. Однак, коли я відповідав, я розміщував те, що мав, і знав з попередніх дописів, що як тільки я пишу асемблер, розумний алек завжди вказує на те, що я мав би робити це з суті справи. Коли я розвиваюсь, я спочатку пишу ассемблер, коли мені подобається результат, я переходжу до внутрішньої роботи .. Це я .. Я просто трапився, коли я опублікував свою відповідь, коли у мене був лише тестовий варіант асемблера.
Андер Седроній

16

Це ще одне рішення для людей, які люблять рекурсію.

Ідея проста. Розділіть введення навпіл і поміняйте дві половинки, продовжуйте, поки не досягне єдиного біта.

Illustrated in the example below.

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

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

2. Swap the 2 Halves
    1010     0010

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

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

Done! Output is 01010100

Ось рекурсивна функція її вирішення. (Примітка. Я використовував непідписані вставки, тому він може працювати для входів до sizeof (unsigned int) * 8 біт.

Рекурсивна функція приймає 2 параметри - значення, біти якого потрібно повернути, і кількість бітів у значенні.

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

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

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

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

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

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

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

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

Це вихід:

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

Чи не підходить цей підхід на 24-розрядному прикладі (3-й)? Я не зовсім знайомий з операторами С та бітових операцій, але з вашого пояснення підходу я здогадуюсь 24-> 12-> 6-> 3 (3 біти нерівномірно розділити). Як numBitsі int, якщо розділити 3 на 2 для парам-фути функцій, то вона буде округлена вниз до 1?
Бренан

13

Ну це, звичайно, не буде такою відповіддю, як Метт Дж, але, сподіваємось, вона все-таки буде корисною.

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

Це абсолютно та сама ідея, що і найкращий алгоритм Метта, за винятком того, що існує ця маленька інструкція під назвою BSWAP, яка підміняє байти (а не біти) 64-бітного числа. Так b7, b6, b5, b4, b3, b2, b1, b0 стає b0, b1, b2, b3, b4, b5, b6, b7. Оскільки ми працюємо з 32-розрядним числом, нам потрібно зрушити число, замінене байтами, вниз на 32 біти. Це просто залишає перед нами завдання замінити 8 біт кожного байта, що робиться, і вуаля! були зроблені.

Час: на моїй машині алгоритм Метта пробіг за ~ 0,52 секунди за пробу. Міна пробігла приблизно 0,42 секунди за випробування. На 20% швидше - це не погано, я думаю.

Якщо ви стурбовані наявністю інструкції BSWAP Wikipedia перераховує інструкцію BSWAP як додану до 80846, яка вийшла у 1989 році. Слід зазначити, що Вікіпедія також заявляє, що ця інструкція працює лише на 32-бітових регістрах, що, очевидно, не є випадку на моїй машині, він дуже добре працює лише на 64-бітних регістрах.

Цей метод буде однаково добре працювати для будь-якого інтегрального типу даних, тому метод можна тривіально узагальнити, передавши бажане число байтів:

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

яке потім можна назвати так:

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

Компілятор повинен бути в змозі оптимізувати додатковий параметр (припускаючи, що компілятор вбудовує функцію), і для sizeof(size_t)випадку правильний зсув буде видалений повністю. Зауважте, що GCC принаймні не в змозі видалити BSWAP і змінити праву, якщо вона передана sizeof(char).


2
Відповідно до Референтного обсягу 2A Інструкції Intel ( intel.com/content/www/us/en/processors/… ) є дві інструкції BSWAP: BSWAP r32 (працює на 32-бітових регістрах), кодований як 0F C8 + rd і BSWAP r64 (працює на 64-бітових регістрах), кодований як REX.W + 0F C8 + rd.
Нубок

Ви кажете, що його можна використовувати так: "n = reverse (n, sizeof (size_t)); // зворотний 64 біт", однак це дасть лише 32 біт результату, якщо всі константи не розширені до 64 біт, тоді це працює.
rajkosto

@rajkosto станом на C ++ 11, дозволені типи цілих літералів включають, unsigned long long intщо має бути не менше 64 біт, як тут і тут
SirGuy

Гаразд? Я просто кажу, що якщо ви хочете, щоб це працювало на 64-бітових значеннях, ви повинні розширити свої літерали (так, наприклад, вони є 0xf0f0f0f0f0f0f0f0ull), інакше високі 32 біт результату будуть усі 0.
rajkosto

@rajkosto Ах, я неправильно зрозумів ваш перший коментар, я це виправив зараз
SirGuy

13

Відповідь Андер Седроніус забезпечує чудове рішення для людей, які мають процесор x86 з підтримкою AVX2. Для платформ x86 без підтримки AVX або платформ, що не є x86, будь-яка з наступних реалізацій повинна працювати добре.

Перший код - це варіант класичного методу бінарного розподілу, кодований для максимального використання ідіоми зсуву плюс-логіки, корисної для різних процесорів ARM. Крім того, в ньому використовується генерація масок на ходу, яка може бути корисною для процесорів RISC, які в іншому випадку потребують декількох інструкцій для завантаження кожного 32-бітного значення маски. Компілятори для платформ x86 повинні використовувати постійне розповсюдження для обчислення всіх масок під час компіляції, а не час виконання.

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

У томі 4А «Мистецтва комп’ютерного програмування» Д. Кнут показує розумні способи повернення бітів, які дещо дивно вимагають меншої кількості операцій, ніж класичні алгоритми бінарного розподілу. Один такий алгоритм для 32-бітних операндів, якого я не можу знайти в TAOCP, показаний у цьому документі на веб-сайті Hacker's Delight.

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

Використовуючи компілятор Intel C / C ++ компілятор 13.1.3.198, обидві вищевказані функції автоматично векторизують приємне націлювання XMM регістри. Вони також можуть бути векторизовані вручну без великих зусиль.

На моєму IvyBridge Xeon E3 1270v2 за допомогою автоматичного векторизованого коду 100 мільйонів uint32_tслів було перетворено за 0,070 секунд за допомогою brev_classic()та 0,068 секунди brev_knuth(). Я подбав про те, щоб мій показник не був обмежений пропускною здатністю системної пам'яті.


2
@JoelSnyder Я припускаю, що "багато магічних чисел" ви в першу чергу маєте на увазі brev_knuth()? Атрибуція в PDF-файлі від Hacker's Delight, схоже, вказує на те, що ці номери безпосередньо від самого Кнута. Я не можу стверджувати, що достатньо зрозумів опис Кнута, що лежить в основах принципів проектування в TAOCP, щоб пояснити, як отримані константи, або як можна піти про похідні константи та коефіцієнти зсуву для довільних розмірів слів.
njuffa

8

Припускаючи, що у вас є масив бітів, як щодо цього: 1. Починаючи з MSB, натискайте біти в стек один за одним. 2. Поп біти з цього стека в інший масив (або той самий масив, якщо ви хочете заощадити місце), помістивши перший вискакуваний біт в MSB і перейшовши до менш значущих бітів звідти.

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

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

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

3
Це змусило мене посміхнутися :) Я хотів би побачити орієнтир цього рішення C # проти одного з тих, які я окреслив вище в оптимізованому С.
Matt J

ЛОЛ ... Але ей! прикметник "найкращий" у "найкращому алгоритмі" - досить суб'єктивна річ: D
Фредерік Дурня

7

Рідна інструкція ARM "rbit" може робити це з 1 циклом процесора та 1 додатковим регістром процесора, неможливо обіграти.


6

Це не робота для людини! ... але ідеально підходить для машини

Це 2015 рік, 6 років з моменту, коли це питання було вперше задано. Компілятори з тих пір стали нашими майстрами, і наша робота як людей - лише допомагати їм. То який найкращий спосіб дати свої наміри машині?

Повернення бітів настільки поширене, що вам потрібно задуматися, чому постійно зростаючий ISA x86 не включає інструкцію робити це за один раз.

Причина: якщо ви даєте справжній стислий намір компілятору, обробка бітів повинна тривати лише ~ 20 циклів процесора . Дозвольте мені показати, як виготовити реверс () і використовувати його:

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

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

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

Складаючи цю зразкову програму з версією Clang> = 3.6, -O3, -march = native (тестується Haswell), дає код якості ілюстрації за допомогою нових інструкцій AVX2, час виконання 11 секунд, обробка ~ 1 мільярд зворотних () s. Це ~ 10 нс на зворотній (), при .5 нс цикл процесора припускаючи, що 2 ГГц ставить нас у 20 милих циклів процесора.

  • Ви можете помістити 10 зворотних () s часу, необхідного для отримання оперативної пам’яті один раз для одного великого масиву!
  • Ви можете встановити 1 зворотний () час, який потрібен для отримання доступу до кешу L2 LUT двічі.

Caveat: цей зразок коду повинен бути гідним орієнтиром протягом декількох років, але він, зрештою, почне демонструвати свій вік, як тільки компілятори будуть досить розумні, щоб оптимізувати main (), щоб просто роздрукувати кінцевий результат, а не насправді нічого обчислювати. Але наразі це працює у демонстрації reverse ().


Bit-reversal is so common...Я не знаю про це. Я працюю з кодом, який практично щодня опрацьовує дані на бітовому рівні, і я не можу згадати, якби у мене була така конкретна потреба. У яких сценаріях вам це потрібно? - Не те, щоб вирішити самостійно це не цікаву проблему.
500 - Внутрішня помилка сервера

@ 500-InternalServerError Я в кінцевому підсумку потребував цієї функції багато разів у граматичному висновку зі швидкими, стислими структурами даних. Звичайне бінарне дерево, закодоване як бітарна матриця, закінчує висновок про граматику в порядку "великого ендіану". Але для кращого узагальнення, якщо ви будуєте дерево (бітарний масив) з вузлами, якими обмінюється перестановка бітів, перенесені рядки граматики знаходяться в "мало ендіан". Це перемикання дозволяє робити висновки рядків змінної довжини, а не фіксованих цілих розмірів. Ця ситуація спливає багато в ефективному FFT , а також: см en.wikipedia.org/wiki/Bit-reversal_permutation

1
Дякую, мені якось вдалося зрозуміти, що FFT може бути причетний до вашої відповіді :)
500 - Внутрішня помилка сервера

чому всього 20 циклів? Яка архітектура? Чи правда це для всіх надшироких архітектур VLIW майбутнього, поки людство і наші походження не вимернуть? Просто запитання, без відповідей ... Знову до пекла
Quonux


5

Я знаю, що це не C, а ASM:

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

Це працює з носієм, тому ви можете також зберегти прапори


1
Я думаю, ви могли б використовувати ключове слово asm , яке було б досить швидко.
Том

Це навіть не працює. Я думаю, ви хочете rclперевести CF в var1, а не просто, shlякий не читає прапори. (Або adc dx,dx). Навіть із цим виправленням, це смішно повільно, використовуючи повільну loopінструкцію та зберігаючи var1пам’ять! Насправді я думаю, що це повинно створювати вихід в AX, але це зберігає / відновлює старе значення AX над вершиною результату.
Пітер Кордес

4

Реалізація з низькою пам’яттю та найшвидшим.

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

4

Ну, це в основному те саме, що і перший "reverse ()", але це 64 біт і для завантаження з потоку інструкцій потрібна лише одна негайна маска. GCC створює код без стрибків, тому це має бути досить швидким.

#include <stdio.h>

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

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

return val;
#undef ZZZZ
}

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

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

4

Мені було цікаво, як швидко пройде очевидний оберт сировини. На моїй машині (i7 @ 2600) в середньому було 1 500 150 000 ітерацій27.28 ns (за випадковим набором 131 071 64-бітних цілих чисел).

Переваги: ​​об'єм пам'яті, необхідний мало, а код простий. Я б сказав, що він не такий великий. Необхідний час передбачуваний і постійний для будь-якого введення (128 арифметичних операцій SHIFT + 64 логічних операцій І + 64 логічних операцій АБО).

Я порівняв найкращий час, отриманий @Matt J - хто має прийняту відповідь. Якщо я правильно прочитав його відповідь, найкраще, що він отримав, це 0.631739секунди для 1,000,000ітерацій, що призводить до середнього показника 631 nsза обертання.

Фрагмент коду, який я використав, це нижче:

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

@greybeard Я не впевнений, що розумію ваше запитання.
Маріан Адам

дякую за те, що помітили помилку, я виправив наданий зразок коду.
маріан адам

3

Можливо, ви хочете використовувати стандартну бібліотеку шаблонів. Це може бути повільніше, ніж згаданий вище код. Однак мені це здається більш зрозумілим і легшим для розуміння.

 #include<bitset>
 #include<iostream>


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


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

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

2

Родовий

C код. Використовуючи 1 байт вхідних даних номер, як приклад.

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

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

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

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

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

Питання задавали "найефективніший", а не "простий / прямий".
Пітер Кордес

1

Як щодо наступного:

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

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

        }


        return output;
    }

Невеликий і простий (хоча 32-бітний).


Питання задавали "найефективніші"; ми можемо виключати циклічність 32 рази. (І особливо не зміщуючи маску, а також не потрібно переносити результат вниз на LSB)
Пітер Кордес,

1

Я подумав, що це один із найпростіших способів повернути біт. будь ласка, дайте мені знати, чи є якась вада в цій логіці. в основному в цій логіці ми перевіряємо значення біта в положенні. встановіть біт, якщо значення 1 у зворотному положенні.

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

Питання задавали "найефективніший", а не "простий / прямий".
Пітер Кордес

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

    unsigned char n = data;

    while(n)

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

Цікаво, але поділ за змінною часу виконання повільний. kце завжди потужність 2, але компілятори, ймовірно, не докажуть цього і перетворять це на біт-сканування / зрушення.
Пітер Кордес

0

Я думаю, що найпростіший метод, який я знаю, слід наступним чином. MSBє введенням і LSBє "зворотним" виходом:

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

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

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

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

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

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

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

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

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

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


// Testing the functionality of the code

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

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

     b = ReverseBits( a );

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

// Output:
Original: 0000000000010001
Reversed: 1000100000000000

0

Ще одне рішення на основі циклу, яке швидко виходить, коли число низьке (в С ++ для кількох типів)

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

або в C для непідписаного 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

Здається, що багато інших публікацій стурбовані швидкістю (тобто кращий = найшвидший). Що про простоту? Поміркуйте:

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

і сподіваємось, що розумний компілятор оптимізує для вас.

Якщо ви хочете змінити довший список бітів (містять sizeof(char) * nбіти), ви можете скористатися цією функцією, щоб отримати:

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

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

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

Це поверне [10000000, 10101010] на [01010101, 00000001].


У вас є 3 зміщення у внутрішній петлі. Збережіть один за допомогою ith_bit = (c >> i) & 1. Також збережіть SUB, змістивши reversed_charзамість зсуву біта, якщо ви не сподіваєтесь, що він складе на x86 в sub something/ bts reg,regдля встановлення n-го біта в регістрі призначення.
Пітер Кордес

-1

Повернення бітів у псевдокоді

Джерело -> байт, який повинен бути відмінений b00101100 призначення -> Зворотний, також повинен бути непідписаним типом, щоб біт знаків не пропонується вниз

копіювати в темп, щоб оригінал не впливав, також повинен бути непідписаним, щоб біт знаків не зміщувався автоматично

bytecopy = b0010110

LOOP8: // зробіть цей тест 8 разів, якщо байткопія <0 (мінус)

    set bit8 (msb) of reversed = reversed | b10000000 

else do not set bit8

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

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

-1

Моє просте рішення

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

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

1
Що i? Також, що це за магічна константа * 4? Це CHAR_BIT / 2?
Пітер Кордес

-1

Це для 32 біт, нам потрібно змінити розмір, якщо врахувати 8 біт.

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

Читання вхідного цілого числа "num" у порядку LSB-> MSB та зберігання у num_reverse у порядку MSB-> LSB.


1
Ви повинні додати пояснення до коду, щоб зрозуміти його було простіше.
Тунакі

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

3
Як правило, відповіді набагато корисніші, якщо вони включають пояснення того, що призначений для цього коду, і чому це вирішує проблему.
IKavanagh
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.