Efficient Algorithm for Bit Reversal (from MSB->LSB to LSB->MSB) in C

后端 未结 26 1372
情深已故
情深已故 2020-11-22 06:08

What is the most efficient algorithm to achieve the following:

0010 0000 => 0000 0100

The conversion is from MSB->LSB to LSB->MSB. All bits

相关标签:
26条回答
  • 2020-11-22 06:54

    This thread caught my attention since it deals with a simple problem that requires a lot of work (CPU cycles) even for a modern CPU. And one day I also stood there with the same ¤#%"#" problem. I had to flip millions of bytes. However I know all my target systems are modern Intel-based so let's start optimizing to the extreme!!!

    So I used Matt J's lookup code as the base. the system I'm benchmarking on is a i7 haswell 4700eq.

    Matt J's lookup bitflipping 400 000 000 bytes: Around 0.272 seconds.

    I then went ahead and tried to see if Intel's ISPC compiler could vectorise the arithmetics in the reverse.c.

    I'm not going to bore you with my findings here since I tried a lot to help the compiler find stuff, anyhow I ended up with performance of around 0.15 seconds to bitflip 400 000 000 bytes. It's a great reduction but for my application that's still way way too slow..

    So people let me present the fastest Intel based bitflipper in the world. Clocked at:

    Time to bitflip 400000000 bytes: 0.050082 seconds !!!!!

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

    The printf's are for debugging..

    Here is the workhorse:

    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
    

    The code takes 32 bytes then masks out the nibbles. The high nibble gets shifted right by 4. Then I use vpshufb and ymm4 / ymm3 as lookup tables. I could use a single lookup table but then I would have to shift left before ORing the nibbles together again.

    There are even faster ways of flipping the bits. But I'm bound to single thread and CPU so this was the fastest I could achieve. Can you make a faster version?

    Please make no comments about using the Intel C/C++ Compiler Intrinsic Equivalent commands...

    0 讨论(0)
  • 2020-11-22 06:54

    Implementation with low memory and fastest.

    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;
        }
    
    0 讨论(0)
  • 2020-11-22 06:54

    I was curious how fast would be the obvious raw rotation. On my machine (i7@2600), the average for 1,500,150,000 iterations was 27.28 ns (over a a random set of 131,071 64-bit integers).

    Advantages: the amount of memory needed is little and the code is simple. I would say it is not that large, either. The time required is predictable and constant for any input (128 arithmetic SHIFT operations + 64 logical AND operations + 64 logical OR operations).

    I compared to the best time obtained by @Matt J - who has the accepted answer. If I read his answer correctly, the best he has got was 0.631739 seconds for 1,000,000 iterations, which leads to an average of 631 ns per rotation.

    The code snippet I used is this one below:

    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 讨论(0)
  • 2020-11-22 06:56

    Well this certainly won't be an answer like Matt J's but hopefully it will still be useful.

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

    This is exactly the same idea as Matt's best algorithm except that there's this little instruction called BSWAP which swaps the bytes (not the bits) of a 64-bit number. So b7,b6,b5,b4,b3,b2,b1,b0 becomes b0,b1,b2,b3,b4,b5,b6,b7. Since we are working with a 32-bit number we need to shift our byte-swapped number down 32 bits. This just leaves us with the task of swapping the 8 bits of each byte which is done and voila! we're done.

    Timing: on my machine, Matt's algorithm ran in ~0.52 seconds per trial. Mine ran in about 0.42 seconds per trial. 20% faster is not bad I think.

    If you're worried about the availability of the instruction BSWAP Wikipedia lists the instruction BSWAP as being added with 80846 which came out in 1989. It should be noted that Wikipedia also states that this instruction only works on 32 bit registers which is clearly not the case on my machine, it very much works only on 64-bit registers.

    This method will work equally well for any integral datatype so the method can be generalized trivially by passing the number of bytes desired:

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

    which can then be called like:

        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
    

    The compiler should be able to optimize the extra parameter away (assuming the compiler inlines the function) and for the sizeof(size_t) case the right-shift would be removed completely. Note that GCC at least is not able to remove the BSWAP and right-shift if passed sizeof(char).

    0 讨论(0)
  • 2020-11-22 06:57

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

    Reading the input integer "num" in LSB->MSB order and storing in num_reverse in MSB->LSB order.

    0 讨论(0)
  • 2020-11-22 06:59

    This is another solution for folks who love recursion.

    The idea is simple. Divide up input by half and swap the two halves, continue until it reaches single bit.

    Illustrated in the example below.
    
    Ex : If Input is 00101010   ==> Expected output is 01010100
    
    1. Divide the input into 2 halves 
        0010 --- 1010
    
    2. Swap the 2 Halves
        1010     0010
    
    3. Repeat the same for each half.
        10 -- 10 ---  00 -- 10
        10    10      10    00
    
        1-0 -- 1-0 --- 1-0 -- 0-0
        0 1    0 1     0 1    0 0
    
    Done! Output is 01010100
    

    Here is a recursive function to solve it. (Note I have used unsigned ints, so it can work for inputs up to sizeof(unsigned int)*8 bits.

    The recursive function takes 2 parameters - The value whose bits need to be reversed and the number of bits in the value.

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

    This is the output:

    Bit Reversal Input = 0x55 Output = 0xaa
    Bit Reversal Input = 0xabcd Output = 0xb3d5
    Bit Reversal Input = 0x123456 Output = 0x651690
    Bit Reversal Input = 0x11223344 Output = 0x22cc4488
    
    0 讨论(0)
提交回复
热议问题