What is the fastest way to transpose the bits in an 8x8 block on bits?

Viewed 5831

I'm not sure the exact term for what I'm trying to do. I have an 8x8 block of bits stored in 8 bytes, each byte stores one row. When I'm finished, I'd like each byte to store one column.

For example, when I'm finished:

Byte0out = Byte0inBit0 + Bit0inByte1 + Bit0inByte2 + Bit0inByte3 + ...
Byte1out = Bit1inByte0 + Bit1inByte1 + Bit1inByte2 + Bit1inByte3 + ...

What is the easiest way to do this in C which performs well? This will run on a dsPIC microcontroller

7 Answers

This is similar to the get column in a bitboard problem and can be solved efficiently by considering those input bytes as 8 bytes of a 64-bit integer. If bit 0 is the least significant one and byte 0 is the first byte in the array then I assume you want to do the following

                              Column 7 becomes...
                              ↓
[ b07 b06 b05 b04 b03 b02 b01 b00   [ b70 b60 b50 b40 b30 b20 b10 b00 ← row 0
  b17 b16 b15 b14 b13 b12 b11 b10     b71 b61 b51 b41 b31 b21 b11 b01
  b27 b26 b25 b24 b23 b22 b21 b20     b72 b62 b52 b42 b32 b22 b12 b02
  b37 b36 b35 b34 b33 b32 b31 b30  →  b73 b63 b53 b43 b33 b23 b13 b03
  b47 b46 b45 b44 b43 b42 b41 b40  →  b74 b64 b54 b44 b34 b24 b14 b04
  b57 b56 b55 b54 b53 b52 b51 b50     b75 b65 b55 b45 b35 b25 b15 b05
  b67 b66 b65 b64 b63 b62 b61 b60     b76 b66 b56 b46 b36 b26 b16 b06
  b77 b76 b75 b74 b73 b72 b71 b70 ]   b77 b67 b57 b47 b37 b27 b17 b07 ]

with bXY is byte X's bit number Y. In that form rotating the left-most column is just packing all the most significant bits into a single byte in reverse order, and similarly other columns can be rotated

To do that we mask out all the last 7 columns and read the array as an uint64_t. The result is

0b h0000000 g0000000 f0000000 e0000000 d0000000 c0000000 b0000000 a0000000
   ↑        ↑        ↑        ↑        ↑        ↑        ↑        ↑
   b77      b67      b57      b47      b37      b27      b17      b07

in little endian, with abcdefgh are b07 to b77 respectively. Now we just need to multiply that value with the magic number 0x0002040810204081 to make a value with hgfedcba in the MSB which is what we expected

uint8_t transpose_column(uint64_t matrix, unsigned col)
{
    const uint64_t column_mask = 0x8080808080808080ull;
    const uint64_t magic       = 0x0002040810204081ull;
    
    return ((matrix << col) & column_mask) * magic >> 56;
}

uint64_t block8x8;
memcpy(&block8x8, bytes_in, sizeof(block8x8));
#if __BYTE_ORDER == __BIG_ENDIAN
block8x8 = swap_bytes(block8x8);
#endif

for (unsigned i = 0; i < 8; i++)
    byte_out[i] = transpose_column(block8x8, 7 - i);

Because you treat the 8-byte array as uint64_t, you may need to align the array properly to get better performance because that way only a single memory load is needed


In AVX2 Intel introduced the PDEP instruction (accessible via the _pext_u64 intrinsic) in the BMI2 instruction set for this purpose so the function can be done in a single instruction

data[i] = _pext_u64(matrix, column_mask << (7 - col));

But unfortunately this won't work in dsPIC as you expected

More ways to transpose the array can be found in the chess programming wiki

Related