【问题标题】:Efficient Algorithm for Bit Reversal (from MSB->LSB to LSB->MSB) in CC中位反转的高效算法(从MSB->LSB到LSB->MSB)
【发布时间】:2010-10-19 06:31:20
【问题描述】:

实现以下目标的最有效算法是什么:

0010 0000 => 0000 0100

转换是从 MSB->LSB 到 LSB->MSB。所有位必须颠倒;也就是说,这不是字节顺序交换。

【问题讨论】:

  • 我认为合适的名称是按位运算。
  • 我认为您的意思是反转,而不是旋转。
  • 大多数 ARM 处理器都有内置的操作。 ARM Cortex-M0 没有,我发现使用每字节表交换位是最快的方法。
  • 另见 Sean Eron Anderson 的 Bit Twiddling Hacks
  • 请定义“最佳”

标签: c algorithm bit-manipulation


【解决方案1】:

注意:下面的所有算法都是用 C 语言编写的,但应该可以移植到您选择的语言中(当它们不那么快时不要看着我 :)

选项

内存不足(32位int,32位机器)(来自here):

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 page

最快(查找表)

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 位 ints,或以内存换取速度(假设您的 L1 数据缓存足够大),并使用 64K 条目查找表一次反转 16 位。


其他

简单

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

结果

我对两个最有前途的解决方案进行了基准测试,查找表和按位与(第一个)。测试机是配备 4GB DDR2-800 和 Core 2 Duo T7500 @ 2.4GHz、4MB L2 缓存的笔记本电脑; YMMV。我在 64 位 Linux 上使用了 gcc 4.3.2。 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 次试验,每次试验反转了 1 亿随机 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 的查找表(字节寻址速度很慢)。如果您需要从系统中挤出内存的最后一个字节(如果您关心位反转的性能,您可能会这样做),按位与方法的优化版本也不会太差。

警告

是的,我知道基准代码完全是 hack。关于如何改进它的建议非常受欢迎。我知道的事情:

  • 我无权访问 ICC。这可能会更快(如果您可以对此进行测试,请在评论中回复)。
  • 64K 查找表可能适用于一些具有大型 L1D 的现代微架构。
  • -mtune=native 不适用于 -O2/-O3(ld 出现了一些疯狂的符号重新定义错误),所以我认为生成的代码不适合我的微架构。
  • 可能有一种方法可以使用 SSE 稍微快一些。我不知道怎么做,但是通过快速复制、打包的按位与和 swizzling 指令,那里一定有一些东西。
  • 我知道只有足够的 x86 程序集是危险的;这是选项 1 在 -O3 上生成的 GCC 代码,所以比我更有知识的人可以查看:

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

编辑:我还尝试在我的机器上使用 uint64_t 类型来查看是否有任何性能提升。性能比 32 位快约 10%,无论您是一次仅使用 64 位类型来反转两个 32 位 int 类型的位,还是实际上将位反转为一半,性能几乎相同许多 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

【讨论】:

  • -1 表示过于详细和彻底的帖子。 j/k。 +1。
  • 这是一个有趣的练习,如果不是那么充实的话。如果不出意外,我希望看到这个过程对其他可能想要对更有价值的东西进行基准测试的人是有建设性的:)
  • 我的……上帝!我想我找到了……很可能是……一个真正的标本。我将不得不查阅我的文件,并做进一步的研究,但有件事告诉我(上帝,帮助我),这是迄今为止 Stack Overflow 迄今为止最伟大、最彻底和最有用的答案。即使是约翰斯基特也会感到震惊和印象深刻!
  • 请记住,微基准测试的一个特殊缺陷(在许多其他缺陷中)是它倾向于人为地支持基于查找表的解决方案。由于基准测试是在循环中重复一个操作,因此通常会发现使用恰好适合 L1 的查找表是最快的,因为完全没有缓存压力,所以每次都会在 L1 中命中所有内容。在实际用例中,该操作通常会与其他导致一些缓存压力的操作交错。未命中 RAM 可能需要比平时长 10 或 100 倍的时间,但这在基准测试中会被忽略。
  • 结果是,如果两个解决方案接近,我通常会选择非 LUT 解决方案(或具有较小 LUT 的解决方案),因为 LUT 对现实世界的影响可能很严重。更好的方法是“就地”对每个解决方案进行基准测试——在更大的应用程序中实际使用它,并提供真实的输入。当然,我们并不总是有时间做这些,而且我们并不总是知道什么是现实的输入。
【解决方案2】:

这个线程引起了我的注意,因为它处理一个简单的问题,即使是现代 CPU 也需要大量工作(CPU 周期)。有一天,我也站在那里,遇到了同样的 ¤#%"#" 问题。我不得不翻转数百万字节。但是我知道我所有的目标系统都是基于现代英特尔的,所以让我们开始优化到极致吧!!!

所以我使用了 Matt J 的查找代码作为基础。我的基准测试系统是 i7 haswell 4700eq。

Matt J 的查找位翻转 400 000 000 字节:大约 0.272 秒。

然后我继续尝试查看英特尔的 ISPC 编译器是否可以对 reverse.c 中的算术进行矢量化。

我不会在这里对我的发现感到厌烦,因为我尝试了很多来帮助编译器找到东西,无论如何,我最终以大约 0.15 秒的性能完成了 400 000 000 字节的位翻转。这是一个很大的减少,但对于我的应用程序来说仍然太慢了..

所以大家让我介绍一下世界上最快的基于 Intel 的 bitflipper。计时:

位翻转 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 用于调试..

这是主力:

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 作为查找表。我可以使用单个查找表,但是在再次将半字节进行 OR 运算之前,我必须向左移动。

还有更快的翻转位的方法。但我必须使用单线程和 CPU,所以这是我能达到的最快速度。你能做一个更快的版本吗?

请不要对使用英特尔 C/C++ 编译器内在等效命令做出任何解释...

【讨论】:

  • 你应该得到比这更多的支持。我知道 pshub 应该可以做到这一点,因为毕竟最好的 popcount 也用它完成了!如果不是为了你,我早就写在这里了。荣誉。
  • 谢谢! 'popcnt' 是我最喜欢的另一个主题;)查看我的 BMI2 版本:result=__tzcnt_u64(~_pext_u64(data[i],data[i]));
  • 将 asm 文件命名为:bitflip_asm.s 然后:yasm -f elf64 bitflip_asm.s 将 c 文件命名为:bitflip.c 然后:g++ -fopenmp bitflip.c bitflip_asm.o -o bitflip 就是这样。
  • Intel CPU 在端口 1 上都有popcnttzcntpext 的执行单元。因此,每个pexttzcnt 都会消耗popcnt 的吞吐量。如果您的数据在 L1D 缓存中很热,那么在 Intel CPU 上对阵列进行 popcount 的最快方法是使用 AVX2 pshufb。 (Ryzen 每个时钟有 4 个 popcnt 吞吐量,所以这可能是最佳的,但 Bulldozer 系列每 4 个时钟有一个 popcnt r64,r64 吞吐量 ...agner.org/optimize)。
  • 我自己使用的是内部函数版本。但是,当我确实回答时,我发布了我所拥有的内容,并且我从以前的帖子中知道,一旦我编写汇编程序,聪明的 aleck 总是指出我应该在内部函数中完成它。当我开发时,我首先编写汇编程序,当我喜欢结果时,我转向内在函数。这就是我。我只是碰巧发布了我的答案,当时我只有“测试”汇编程序版本。
【解决方案3】:

嗯,这肯定不会像 Matt J 那样给出答案,但希望它仍然有用。

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 位的任务,瞧!我们完成了。

时间:在我的机器上,Matt 的算法每次试验的运行时间约为 0.52 秒。我的每次试验运行时间约为 0.42 秒。我认为快 20% 还不错。

如果您担心指令 BSWAP Wikipedia 的可用性,则将指令 BSWAP 与 1989 年推出的 80846 一起添加。需要注意的是,维基百科还声明该指令仅适用于 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) 的情况,右移将被完全删除。请注意,如果通过sizeof(char),GCC 至少无法删除 BSWAP 和右移。

【讨论】:

  • 根据 Intel 指令集参考卷 2A (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));//reverse 64 bits" 但是这只会给出 32bits 的结果,除非所有的常量都被扩展到64位,然后就可以了。
  • @rajkosto 从 C++11 开始,允许的整数文字类型包括 unsigned long long int,它必须至少为 64 位,根据 herehere
  • 好吗?我只是说,如果您希望它适用于 64 位值,则必须扩展您的文字(例如,它们是 0xf0f0f0f0f0f0f0f0ull ),否则结果的高 32 位将全为 0。
  • @rajkosto 啊,我误解了你的第一条评论,我现在已经解决了
【解决方案4】:

对于喜欢递归的人来说,这是另一种解决方案。

这个想法很简单。 将输入除以一半并交换两半,继续直到达到单个位。

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 个)上不起作用吗?我对 C 和按位运算符不太熟悉,但根据您对方法的解释,我猜测 24->12->6->3(3 位不均匀拆分)。由于numBits 是 int,当您将函数参数除以 3 时,它会向下舍入为 1?
【解决方案5】:

Anders Cedronius's answer 为拥有支持 AVX2 的 x86 CPU 的人们提供了一个很好的解决方案。对于不支持 AVX 的 x86 平台或非 x86 平台,以下任一实现都应该可以正常工作。

第一个代码是经典二进制分区方法的变体,其编码是为了最大限度地利用在各种 ARM 处理器上有用的 shift-plus-logic 习惯用法。此外,它使用动态掩码生成,这对于需要多条指令来加载每个 32 位掩码值的 RISC 处理器可能是有益的。 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;
}

在“计算机编程的艺术”的第 4A 卷中,D. Knuth 展示了巧妙的位反转方法,这些方法令人惊讶地比经典的二进制分区算法需要更少的操作。 Hacker's Delight 网站上的this document 显示了一种用于 32 位操作数的算法,我在 TAOCP 中找不到。

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

使用英特尔编译器 C/C++ 编译器 13.1.3.198,上述两个函数都可以自动矢量化,很好地针对 XMM 寄存器。它们也可以手动矢量化,而无需付出太多努力。

在我的 IvyBridge Xeon E3 1270v2 上,使用自动矢量化代码,使用 brev_classic() 在 0.070 秒内对 1 亿个 uint32_t 字进行位反转,使​​用 brev_knuth() 在 0.068 秒内进行位反转。我注意确保我的基准测试不受系统内存带宽的限制。

【讨论】:

  • @JoelSnyder 我假设您主要指的是“大量神奇数字”brev_knuth()?来自 Hacker's Delight 的 PDF 中的归属似乎表明这些数字直接来自 Knuth 本人。我不能声称已经充分理解了 Knuth 对 TAOCP 中基本设计原则的描述,以解释常量是如何导出的,或者人们将如何处理任意字长的导出常量和移位因子。
【解决方案6】:

假设你有一个位数组,这个怎么样: 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();
}

【讨论】:

  • 这个让我笑了 :) 我很想看到这个 C# 解决方案与我上面在优化的 C 中概述的一个基准。
  • 大声笑...但是,嘿! “最佳算法”中的形容词“最佳”是一个非常主观的东西:D
【解决方案7】:

原生 ARM 指令“rbit”可以用 1 个 cpu 周期和 1 个额外的 cpu 寄存器来完成,无法超越。

【讨论】:

    【解决方案8】:

    这不是人类的工作! ...但对于机器来说是完美的

    这是 2015 年,距离第一次提出这个问题已有 6 年。编译器从此成为我们的主人,而我们作为人类的工作只是帮助他们。那么,将我们的意图传达给机器的最佳方式是什么?

    位反转如此普遍,以至于您不得不想知道为什么 x86 不断增长的 ISA 不包含一次性执行的指令。

    原因:如果您将真正简洁的意图提供给编译器,位反转应该只需要 ~20 个 CPU 周期。让我向您展示如何制作 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 秒 处理约 10 亿个 reverse()。每个 reverse() 大约 10 ns,假设 2 GHz 的 CPU 周期为 0.5 ns,使我们处于最佳的 20 个 CPU 周期。

    • 对于单个大型数组,您可以在访问 RAM 一次所需的时间内容纳 10 个 reverse()!
    • 您可以在访问 L2 缓存 LUT 两次所需的时间内放入 1 个 reverse()。

    警告:这个示例代码应该可以作为一个不错的基准测试几年,但是一旦编译器足够聪明地优化 main() 以仅打印最终结果而不是真正计算任何东西,它最终会开始显示它的年龄。但目前它适用于展示 reverse()。

    【讨论】:

    • Bit-reversal is so common... 我不知道。我几乎每天都使用处理位级别数据的代码,我不记得曾经有过这种特定需求。在什么场景下需要它? - 并不是说​​它本身就不是一个有趣的问题。
    • @500-InternalServerError 在快速、简洁的数据结构的语法推理中,我最终需要这个函数很多次。编码为位数组的普通二叉树最终会以“大端”顺序推断语法。但是为了更好的概括,如果你构建一个树(位数组),其中节点通过位反转排列交换,学习语法的字符串是“小端”。这种切换允许您推断可变长度的字符串,而不是固定的整数大小。这种情况在高效 FFT 中也经常出现:参见 en.wikipedia.org/wiki/Bit-reversal_permutation
    • 谢谢,我以某种方式设法直觉 FFT 可能与您的回答有关 :)
    • 为什么只有 20 个周期?哪种架构?这对于未来所有超宽 VLIW 架构是否都是如此,直到人类和我们的血统消失?只是问题,没有答案......再次投下地狱
    【解决方案9】:

    当然,bit-twiddling hack 的明显来源在这里: http://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious

    【讨论】:

      【解决方案10】:

      以低内存和最快的速度实现。

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

      【讨论】:

        【解决方案11】:

        嗯,这与第一个“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;
        }
        

        【讨论】:

          【解决方案12】:

          我很好奇明显的原始旋转会有多快。 在我的机器 (i7@2600) 上,1,500,150,000 次迭代的平均值为 27.28 ns(在 131,071 个 64 位整数的随机集合上)。

          优点:所需内存少,代码简单。我会说它也没有那么大。对于任何输入(128 次算术 SHIFT 运算 + 64 次逻辑 AND 运算 + 64 次逻辑 OR 运算),所需的时间都是可预测且恒定的。

          我与@Matt J 获得的最佳时间进行了比较,后者的答案已被接受。如果我正确地阅读了他的答案,他得到的最佳结果是 0.6317391,000,000 迭代,这导致平均每次旋转 631 ns

          我使用的代码sn-p是下面这个:

          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 我不确定我是否理解你的问题。
          • 感谢您注意到这个错误,我修复了提供的代码示例。
          【解决方案13】:

          您可能想要使用标准模板库。它可能比上面提到的代码慢。但是,在我看来,它更清晰,更容易理解。

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

          【讨论】:

            【解决方案14】:

            通用

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

            【讨论】:

            • 问题要求“最有效”,而不是“简单/直接”。
            【解决方案15】:

            以下内容如何:

                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)
            【解决方案16】:

            我认为这是反转位的最简单方法之一。 如果这个逻辑有任何缺陷,请告诉我。 基本上在这个逻辑中,我们检查位置位的值。 如果值是 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;    
            }    
            

            【讨论】:

            • 问题要求“最有效”,而不是“简单/直接”。
            【解决方案17】:
            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 的幂,但编译器可能不会证明这一点并将其转换为位扫描/移位。
            【解决方案18】:

            我认为我知道的最简单的方法如下。 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.
            

            【讨论】:

              【解决方案19】:
              // 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
              

              【讨论】:

                【解决方案20】:

                另一种基于循环的解决方案,在数量较少时快速退出(在 C++ 中用于多种类型)

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

                【讨论】:

                  【解决方案21】:

                  似乎许多其他帖子都在关注速度(即最好 = 最快)。 简单又如何?考虑:

                  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 &gt;&gt; i) &amp; 1 保存一个。也可以通过移动 reversed_char 而不是移动位来保存 SUB,除非您希望它在 x86 上编译为 sub something / bts reg,reg 以设置目标寄存器中的第 n 位。
                  【解决方案22】:

                  高效可能意味着吞吐量或延迟。

                  对于自始至终,请参阅 Anders Cedronius 的答案,这是一个很好的答案。

                  为了降低延迟,我推荐以下代码:

                  uint32_t reverseBits( uint32_t x )
                  {
                  #if defined(__arm__) || defined(__aarch64__)
                      __asm__( "rbit %0, %1" : "=r" ( x ) : "r" ( x ) );
                      return x;
                  #endif
                      // Flip pairwise
                      x = ( ( x & 0x55555555 ) << 1 ) | ( ( x & 0xAAAAAAAA ) >> 1 );
                      // Flip pairs
                      x = ( ( x & 0x33333333 ) << 2 ) | ( ( x & 0xCCCCCCCC ) >> 2 );
                      // Flip nibbles
                      x = ( ( x & 0x0F0F0F0F ) << 4 ) | ( ( x & 0xF0F0F0F0 ) >> 4 );
                  
                      // Flip bytes. CPUs have an instruction for that, pretty fast one.
                  #ifdef _MSC_VER
                      return _byteswap_ulong( x );
                  #elif defined(__INTEL_COMPILER)
                      return (uint32_t)_bswap( (int)x );
                  #else
                      // Assuming gcc or clang
                      return __builtin_bswap32( x );
                  #endif
                  }
                  

                  编译器输出:https://godbolt.org/z/5ehd89

                  【讨论】:

                    【解决方案23】:

                    伪代码中的位反转

                    source -> 要反转的字节 b00101100 目的地 -> 反转,也需要是无符号类型,因此符号位不会向下传播

                    复制到临时文件中,因此原始文件不受影响,还需要为无符号类型,以便符号位不会自动移入

                    bytecopy = b0010110
                    

                    LOOP8: // 重复 8 次 测试 bytecopy 是否

                        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.
                    

                    【讨论】:

                      【解决方案24】:

                      我的简单解决方案

                      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;
                      

                      【讨论】:

                      • i 是什么?另外,那个神奇的常数* 4 是什么?是CHAR_BIT / 2吗?
                      【解决方案25】:

                      这是针对 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);
                          }
                      

                      以LSB->MSB顺序读取输入整数“num”并以MSB->LSB顺序存储在num_reverse中。

                      【讨论】:

                      • 您应该在代码中添加一个解释,以便更容易理解。
                      猜你喜欢
                      • 2013-05-17
                      • 2013-12-24
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-11-09
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多