Наиболее эффективный алгоритм обращения битов (от MSB-> LSB к LSB-> MSB) в C

218

Каков наилучший алгоритм для достижения следующего:

0010 0000 => 0000 0100

Преобразование происходит из MSB- > LSB в LSB- > MSB. Все биты должны быть отменены; т.е. это не подстановка под индексы.

  • 1
    Я думаю, что подходящее имя является побитовой операцией.
  • 3
    Я думаю, что вы имели в виду разворот, а не вращение.
Показать ещё 5 комментариев
Теги:
algorithm
bit-manipulation

28 ответов

457
Лучший ответ

ПРИМЕЧАНИЕ. Все алгоритмы ниже приведены на 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));

}

Из знаменитой страницы Бит 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 s или обменять память на скорость (при условии, что ваш 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 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) использовались для таймеров с высоким разрешением.

reverse.c

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

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

}

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

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

    free(ints);
    free(ints2);

    return 0;
}

reverse_lookup.c

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

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

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

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

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

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

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

    free(ints);
    free(ints2);

    return 0;
}

Я пробовал оба подхода при нескольких различных оптимизации, выполнял 3 испытания на каждом уровне, и каждое исследование отменяет 100 миллионов случайных неподписанных 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. Это может быть быстрее (просьба ответить в комментарии, если вы можете проверить это).
  • Таблица поиска в 64 КБ может превзойти некоторые современные микроархитектуры с большим L1D.
  • -mtune = native не работал для -O2/-O3 (ld взорвался с некоторой ошибкой переопределения символа), поэтому я не верю, что сгенерированный код настроен для моей микроархитектуры.
  • Возможно, есть способ сделать это немного быстрее с помощью SSE. Я понятия не имею, как, но с быстрой репликацией, упакованными побитовыми И и swizzling инструкциями там должно быть что-то.
  • Я знаю, что только сборка 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-битные типы для обратного бита в двух 32-битных ints за раз или вы фактически меняете биты в два раза больше, бит. Код сборки показан ниже (для первого случая, биты реверса для 2 32-битных ints за раз):

.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
    Я думаю, что 8-битная таблица должна быть самой быстрой на большинстве архитектур, но только тесты покажут.
  • 0
    Вы правы! Смотрите результаты выше (через минуту) :)
Показать ещё 17 комментариев
51

Эта тема привлекла мое внимание, поскольку она касается простой проблемы, требующей большой работы (циклов процессора) даже для современного процессора. И однажды я также стоял там с той же проблемой ¤ #% "#". Мне пришлось перевернуть миллионы байтов. Однако я знаю, что все мои целевые системы - это современные процессоры Intel, поэтому давайте начнем оптимизировать до крайности!!!

Итак, я использовал код поиска Matt J как базу. система, с которой я сравниваю, - это i7 haswell 4700eq.

Мэтт-Джей-битрейт 400 000 000 байтов: около 0,272 секунды.

Затем я пошел дальше и попытался выяснить, может ли компилятор Intels ISPC прорисовать арифметику в обратном направлении.

Я не собираюсь утомлять вас своими выводами здесь, так как я много старался помочь компилятору найти материал, так или иначе, у меня получилось производительность около 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;
}

Притф для отладки..

Вот рабочая лошадка:

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 как таблицы поиска. Я мог бы использовать единую таблицу поиска, но тогда мне пришлось бы сдвинуть влево, прежде чем снова соединить кусочки.

Есть еще более быстрые способы переброски бит. Но я привязан к одному потоку и процессору, так что это было самым быстрым результатом. Вы можете сделать более быструю версию?

Пожалуйста, не комментируйте использование встроенных эквивалентных команд компилятора Intel C/С++...

  • 1
    Черт возьми, парень, ты должен получить 100 тысяч голосов на этом сайте.
  • 2
    Вы заслуживаете FAR больше голосов, чем это. Я знал, что это должно быть выполнимо с pshub , потому что, в конце концов, лучший попконт также делается с этим! Я бы написал это здесь, если бы не ты. Престижность.
Показать ещё 13 комментариев
13

Это еще одно решение для людей, которые любят рекурсию.

Идея проста. Разделите вход на половину и замените две половины, продолжайте, пока он не достигнет одного бита.

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

Вот рекурсивная функция для ее решения. (Примечание. Я использовал unsigned ints, поэтому он может работать для входов до 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
  • 0
    Этот подход не работает на 24-битном примере (3-й)? Я не совсем знаком с C и побитовыми операторами, но из вашего объяснения подхода я предполагаю 24-> 12-> 6-> 3 (3 бита неравномерно для разделения). Поскольку numBits - это int, когда вы numBits 3 на 2 для параметра функции, оно будет округлено до 1?
9

Ну, это, конечно, не будет ответом, как Мэтт Дж, но, надеюсь, он по-прежнему будет полезен.

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

Это точно та же идея, что и лучший алгоритм Matt, за исключением того, что есть эта небольшая инструкция под названием 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
    Согласно справочному руководству Intel по набору томов 2A ( intel.com/content/www/us/en/processors/… ) есть две инструкции BSWAP: BSWAP r32 (работает на 32-разрядных регистрах), который кодируется как 0F C8 + rd и BSWAP r64 (работает на 64-битных регистрах), который кодируется как REX.W + 0F C8 + rd.
  • 0
    Вы говорите, что это можно использовать так: «n = reverse (n, sizeof (size_t)); // реверсировать 64 бита», однако это даст только 32 бита результата, если все константы не расширены до 64 бита, тогда это работает.
Показать ещё 3 комментария
8

Предполагая, что у вас есть массив бит, как насчет этого:  1. Начиная с MSB, вставляйте биты в стек один за другим.  2. Поп-биты из этого стека в другой массив (или тот же массив, если вы хотите сэкономить место), поместив первый бит в MSB и перейдя к менее значительным битам.

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

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

for (int i = 0; i < bits.Length; i++)
{
    bits[i] = stack.pop();
}
  • 3
    Это заставило меня улыбнуться :) Я хотел бы увидеть эталон этого решения C # по сравнению с одним из тех, что я обрисовал в общих чертах в оптимизированном C.
  • 0
    LOL ... но эй! прилагательное «лучший» в «лучшем алгоритме» - вещь довольно субъективная: D
7

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

Первый код - это вариант классического метода двоичного разбиения, закодированный для максимизации использования идиомы shift-plus-logic, полезной для различных 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 Delight.

/* Knuth 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/С++ 13.1.3.198, обе указанные функции автоматически делят на векторию красиво предназначенные XMM регистры. Их также можно было бы векторизовать вручную без особых усилий.

На моем IvyBridge Xeon E3 1270v2 с использованием автоиндексированного кода 100 миллионов слов uin32_t были восстановлены по битам в 0.070 секунд с использованием brev_classic() и 0,068 секунд с использованием brev_knuth(). Я позаботился о том, чтобы мой бенчмарк не ограничивался пропускной способностью системной памяти.

  • 1
    @JoelSnyder Полагаю, под «множеством магических чисел» вы в первую очередь ссылаетесь на brev_knuth() ? Атрибуция в PDF от Восхищения Хакера, кажется, указывает, что эти числа непосредственно от самого Кнута. Я не могу утверждать, что достаточно хорошо понял описание Кнутом основополагающих принципов проектирования в TAOCP, чтобы объяснить, как были получены константы или как можно было бы вывести константы и коэффициенты сдвига для произвольных размеров слов.
6

Я знаю, что это не 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 , что было бы довольно быстро.
  • 0
    Это даже не работает. Я думаю, вы хотите, чтобы rcl CF в var1 , а не просто shl который не читает флаги. (Или adc dx,dx ). Даже с этим исправлением это смехотворно медленно, используя инструкцию замедленного loop и сохраняя var1 в памяти! На самом деле я думаю, что это должно производить выходные данные в AX, но это сохраняет / восстанавливает старое значение AX поверх результата.
5

Конечно, очевидный источник взлохмаченных хаков находится здесь: http://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious

4

Инструкция Native ARM "rbit" может сделать это с циклом 1 cpu и 1 дополнительным регистром процессора, невозможно превзойти.

4

Это не работа для человека!... но идеально подходит для машины

Это 2015 год, 6 лет с того момента, когда этот вопрос был впервые задан. Составители с тех пор стали нашими мастерами, и наша работа как людей - только помочь им. Итак, что лучший способ дать наши намерения машине?

Бит-реверс настолько распространен, что вам нужно задаться вопросом, почему x86, когда-либо растущая ISA, не включает в себя инструкцию, чтобы сделать это один раз.

Причина: если вы даете свое сжатое намерение компилятору, бит-реверс должен принимать только ~ 20 циклов процессора. Позвольте мне показать вам, как обрабатывать reverse() и использовать его:

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

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

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

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

  • Вы можете поместить 10 обратных() s за время, необходимое для доступа к ОЗУ один раз для одного большого массива!
  • Вы можете установить 1 обратный() за время, необходимое для доступа к LUT-кешу L2 дважды.

Предостережение: этот образец кода должен считаться достойным эталоном в течение нескольких лет, но в конечном итоге он начнет показывать свой возраст, как только компиляторы будут достаточно умны, чтобы оптимизировать main(), чтобы просто распечатать окончательный результат, а не действительно что-либо вычислять. Но пока он работает при демонстрации reverse().

  • 0
    Bit-reversal is so common... Я не знаю об этом. Я работаю с кодом, который работает с данными на битовом уровне практически каждый день, и я не могу вспомнить, чтобы когда-либо имел эту конкретную потребность. В каких сценариях это нужно? - Не то чтобы это не было интересной проблемой для самостоятельного решения.
  • 0
    @ 500-InternalServerError Мне пришлось много раз нуждаться в этой функции в грамматическом выводе с быстрыми, краткими структурами данных. Нормальное двоичное дерево, закодированное в виде битового массива, в конечном итоге выводит грамматику в порядке «с прямым порядком байтов». Но для лучшего обобщения, если вы строите дерево (bitarray) с узлами, заменяемыми перестановкой перестановки битов, строки изученной грамматики находятся в «little-endian». Такое переключение позволяет выводить строки переменной длины, а не фиксированные целочисленные размеры. Эта ситуация также часто возникает в эффективных БПФ: см. En.wikipedia.org/wiki/Bit-reversal_permutation
Показать ещё 2 комментария
4

Возможно, вы захотите использовать стандартную библиотеку шаблонов. Это может быть медленнее, чем вышеупомянутый код. Однако мне кажется яснее и понятнее.

 #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;  
 }
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;
    }
  • 3
    Не похоже на C для меня ...
3

Мне было любопытно, насколько быстрым будет очевидное необработанное вращение. На моей машине (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);
}
  • 0
    @greybeard Я не уверен, что понимаю ваш вопрос.
  • 0
    спасибо за замечание ошибки, я исправил предоставленный пример кода.
3

Как насчет следующего:

    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-разрядный).

  • 0
    Вопрос, заданный для «наиболее эффективного»; мы можем исключить зацикливание 32 раза. (И особенно не сдвигая маску, а также необходимость сдвигать результат до LSB)
3

Generic

C-код. Использование 1 байта входных данных num в качестве примера.

    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);
  • 0
    Вопрос задан как «самый эффективный», а не «простой / простой».
2

Ну, это в основном то же самое, что и первое "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;
}
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
    Вопрос задан как «самый эффективный», а не «простой / простой».
0

Мое простое решение

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 ?
0

Похоже, что многие другие сообщения касаются скорости (лучше всего - быстрее). Как насчет простоты? Рассмотрим:

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

и надеемся, что умный компилятор оптимизирует для вас.

Если вы хотите отменить более длинный список бит (содержащий sizeof(char) * n bits), вы можете использовать эту функцию для получения:

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

  • 0
    У вас есть 3 смены во внутреннем цикле. Сохраните один с помощью ith_bit = (c >> i) & 1 . Также сохраните SUB, сдвигая reversed_char вместо сдвига бита, если только вы не надеетесь, что он скомпилирует на x86 для bts reg,reg sub something / bts reg,reg для установки n-го бита в регистре назначения.
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 для unsigned 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
This is for 32 bit, we need to change the size if we consider 8 bits. 

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

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

  • 1
    Вы должны добавить пояснение к коду, чтобы его было легче понять.
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

Я думаю, что самый простой метод, который я знаю, следует. 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
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;
}
  • 0
    Пожалуйста, отформатируйте ваш код
  • 0
    Интересно, но деление на переменную времени выполнения происходит медленно. k всегда является степенью 2, но компиляторы, вероятно, не докажут это и не превратят его в bit-scan / shift.
0

Заданный вопрос заключается в том, чтобы изменить байт (8 бит данных)

typedef unsigned char byte;

byte reverseByte(byte a)
{
    int i;
    byte b = 0;

    for ( i = 0 ; i < 8 ; i ++)
    {
        b <<= 1;
        b |=  ( (a & (1 << i)) >> i);          
    }
    return b; 
}
0

Реверс бит в псевдокоде

источник → байт, подлежащий обратному b00101100  destination → reverseed, также должен быть неподписанного типа, поэтому бит знака не передается вниз

копировать в temp, так что оригинал не затрагивается, также должен быть неподписанного типа, так что бит знака не сдвигается автоматически

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
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
    Как правило, ответы гораздо полезнее, если они включают в себя объяснение того, для чего предназначен код, и почему это решает проблему.
-6
int main() {
    int n;
    scanf("%d", &n);
    while (n) {
        if (n & 1)
            printf("1");
        else
            printf("0");
        n >>= 1;
    }
    printf("\n");
}

Output:
25
10011

Ещё вопросы

Сообщество Overcoder
Наверх
Меню