【问题标题】:What is the fastest way to transpose the bits in an 8x8 block on bits?在位上转置 8x8 块中的位的最快方法是什么?
【发布时间】:2011-10-19 07:53:30
【问题描述】:

我不确定我正在尝试做什么的确切术语。我有一个8x8bits 存储在8 bytes 中,每个字节存储一行。完成后,我希望每个字节存储一列。

例如,当我完成时:

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

C 中执行此操作的 最简单 方法是什么?这将在 dsPIC 微控制器上运行

【问题讨论】:

  • 那么,答案应该是最快还是最简单
  • 我假设你想要 Byte0Out= Byte0inBit0 + Byte1inBit0*2 + ...
  • 您要查找的术语是“转置”。
  • @Casey:这不是代码高尔夫,这是一个真正有用的问题。

标签: c embedded transpose bitarray dspic


【解决方案1】:

这听起来很像在使用位平面的显示器上使用的所谓“Chunky to plane”例程。以下链接对其代码使用 MC68K 汇编器,但提供了一个很好的问题概述(假设我正确理解了这个问题):

http://membres.multimania.fr/amycoders/sources/c2ptut.html

【讨论】:

    【解决方案2】:

    你真的想用 SIMD 指令做这样的事情,比如 GCC 矢量矢量支持:http://ds9a.nl/gcc-simd/example.html

    【讨论】:

    • 那很好,但这需要在 dsPIC 微控制器上运行。
    【解决方案3】:

    Lisp 原型:

    (declaim (optimize (speed 3) (safety 0)))
    (defun bit-transpose (a)
      (declare (type (simple-array unsigned-byte 1) a))
      (let ((b (make-array 8 :element-type '(unsigned-byte 8))))
        (dotimes (j 8)
          (dotimes (i 8)
        (setf (ldb (byte 1 i) (aref b j))
              (ldb (byte 1 j) (aref a i)))))
        b))
    

    这是运行代码的方式:

    #+nil
    (bit-transpose (make-array 8 :element-type 'unsigned-byte
                   :initial-contents '(1 2 3 4 5 6 7 8)))
    ;; => #(85 102 120 128 0 0 0 0)
    

    有时我会反汇编代码以检查是否没有对安全函数的不必要调用。

    #+nil
    (disassemble #'bit-transpose)
    

    这是一个基准。经常运行该函数以处理(二进制)HDTV 图像。

    #+nil
    (time 
     (let ((a (make-array 8 :element-type 'unsigned-byte
                  :initial-contents '(1 2 3 4 5 6 7 8)))
           (b (make-array 8 :element-type 'unsigned-byte
                  :initial-contents '(1 2 3 4 5 6 7 8))))
       (dotimes (i (* (/ 1920 8) (/ 1080 8)))
         (bit-transpose a))))
    

    只用了 51 毫秒。请注意,我使用了很多,因为该函数一直在分配新的 8 字节数组。我确信 C 中的实现可以进行更多调整。

    Evaluation took:
      0.051 seconds of real time
      0.052004 seconds of total run time (0.052004 user, 0.000000 system)
      101.96% CPU
      122,179,503 processor cycles
      1,048,576 bytes consed
    

    这里还有一些测试用例:

    #+nil
    (loop for j below 12 collect
      (let ((l (loop for i below 8 collect (random 255))))
        (list l (bit-transpose (make-array 8 :element-type 'unsigned-byte
                    :initial-contents l)))))
    ;; => (((111 97 195 202 47 124 113 164) #(87 29 177 57 96 243 111 140))
    ;;     ((180 192 70 173 167 41 30 127) #(184 212 221 232 193 185 134 27))
    ;;     ((244 86 149 57 191 65 129 178) #(124 146 23 24 159 153 35 213))
    ;;     ((227 244 139 35 38 65 214 64) #(45 93 82 4 66 27 227 71))
    ;;     ((207 62 236 89 50 64 157 120) #(73 19 71 207 218 150 173 69))
    ;;     ((89 211 149 140 233 72 193 192) #(87 2 12 57 7 16 243 222))
    ;;     ((97 144 19 13 135 198 238 33) #(157 116 120 72 6 193 97 114))
    ;;     ((145 119 3 85 41 202 79 134) #(95 230 202 112 11 18 106 161))
    ;;     ((42 153 67 166 175 190 114 21) #(150 125 184 51 226 121 68 58))
    ;;     ((58 232 38 210 137 254 19 112) #(80 109 36 51 233 167 170 58))
    ;;     ((27 245 1 197 208 221 21 101) #(239 1 234 33 115 130 186 58))
    ;;     ((66 204 110 232 46 67 37 34) #(96 181 86 30 0 220 47 10)))
    

    现在我真的很想看看我的代码与 Andrejs Cainikovs 的 C 解决方案相比如何 (编辑:我认为是错误的):

    #include <string.h>
    
    unsigned char bytes_in[8]={1,2,3,4,5,6,7,8};
    unsigned char bytes_out[8];
    
    /* please fill bytes_in[] here with some pixel-crap */
    void bit_transpose(){
      memset(bytes_out, 0, 8);
      int i,j;
      for(i = 0; i < 8; i++)
        for(j = 0; j < 8; j++) 
          bytes_out[i] = (bytes_out[i] << 1) | ((bytes_in[j] >> (7 - i)) & 0x01);
    }
    
    int
    main()
    {
      int j,i;
      for(j=0;j<100;j++)
        for(i=0;i<(1920/8*1080/8);i++)
          bit_transpose();
      return 0;
    }
    

    并对其进行基准测试:

    wg@hp:~/0803/so$ gcc -O3 trans.c
    wg@hp:~/0803/so$ time ./a.out 
    
    real    0m0.249s
    user    0m0.232s
    sys     0m0.000s
    

    HDTV 图像的每个循环需要 2.5 毫秒。这比我未优化的 Lisp 快得多。

    不幸的是,C 代码给出的结果与我的 lisp 不同:

    #include <stdio.h>
    int
    main()
    {
      int j,i;
      bit_transpose();
      for(i=0;i<8;i++)
        printf("%d ",(int)bytes_out[i]);
      return 0;
    }
    wg@hp:~/0803/so$ ./a.out 
    0 0 0 0 1 30 102 170 
    

    【讨论】:

    • +1,感谢您的巨大努力和口齿不清。一直想学习那种语言,但从来没有通过 emacs 定制 :)
    • 谢谢。一些休闲的 Lisp 总是很适合作为真正工作的休息时间。现在我必须同步硬件,我不方便地设计同步。幸运的是,我也可以在我的主要工作中使用 Lisp :-)
    • 感谢您的努力!我已经更新了我的代码 - 您能否也更新一下您的答案: bytes_out[i] = (bytes_out[i] > (7 - i)) & 0x01);
    【解决方案4】:

    如果您正在寻找最简单的解决方案:

    /* not tested, not even compiled */
    
    char bytes_in[8];
    char bytes_out[8];
    
    /* please fill bytes_in[] here with some pixel-crap */
    
    memset(bytes_out, 0, 8);
    for(int i = 0; i < 8; i++) {
        for(int j = 0; j < 8; j++) {
            bytes_out[i] = (bytes_out[i] << 1) | ((bytes_in[j] >> (7 - i)) & 0x01);
        }
    }
    

    如果您正在寻找最快的解决方案:

    How to transpose a bit matrix in the assembly by utilizing SSE2.

    【讨论】:

    • 我不认为你的代码做换位。也许你需要写
    • 考虑到帖子被标记为“嵌入式”和“C”,并且地球上 99% 的处理器不是 x86 Pentium4+ CPU,您的 SSE2 x86 汇编语言解决方案并不是最有用的.但是考虑到这里有多少响应者提到了 SIMD、x86 ASM 或其他什么,也许我会爬回我的洞里……
    • @whoplist:谢谢,通过将
    • 谢谢你,whoplist。实际上,你看到我作为一个 wordpress 菜鸟的挣扎不小心创建了表情符号 :-) 例如,我现在知道你不能发布像“if (len
    • @Mischa 如果 CPU 支持 SSE2,SSE2 pmovmskb 指令确实非常适合(高效)这些类型的位矩阵操作。另请参阅我的回答 here,它使用 AVX2 vpmovmskb 指令来旋转 8x8 位矩阵。
    【解决方案5】:

    如果您想要优化的解决方案,您可以使用 x86 中的 SSE 扩展。 您需要使用其中的 4 个 SIMD 操作码。 MOVQ - 移动 8 个字节 PSLLW - 压缩左移逻辑字 PMOVMSKB - 打包移动掩码字节 和 2 个常规 x86 操作码 LEA - 加载有效地址 MOV - 移动

    byte[] m = byte[8]; //input
    byte[] o = byte[8]; //output
    LEA ecx, [o]
    // ecx = the address of the output array/matrix
    MOVQ xmm0, [m]
    // xmm0 = 0|0|0|0|0|0|0|0|m[7]|m[6]|m[5]|m[4]|m[3]|m[2]|m[1]|m[0]
    PMOVMSKB eax, xmm0
    // eax = m[7][7]...m[0][7] the high bit of each byte
    MOV [ecx+7], al
    // o[7] is now the last column
    PSLLW xmm0, 1
    // shift 1 bit to the left
    PMOVMSKB eax, xmm0
    MOV [ecx+6], al
    PSLLW xmm0, 1
    PMOVMSKB eax, xmm0
    MOV [ecx+5], al
    PSLLW xmm0, 1
    PMOVMSKB eax, xmm0
    MOV [ecx+4], al
    PSLLW xmm0, 1
    PMOVMSKB eax, xmm0
    MOV [ecx+3], al
    PSLLW xmm0, 1
    PMOVMSKB eax, xmm0
    MOV [ecx+2], al
    PSLLW xmm0, 1
    PMOVMSKB eax, xmm0
    MOV [ecx+1], al
    PSLLW xmm0, 1
    PMOVMSKB eax, xmm0
    MOV [ecx], al
    

    25 个 x86 操作码/指令,而不是具有 64 次迭代的堆叠 for...loop 解决方案。 抱歉,该符号不是 c/c++ 编译器接受的 ATT 样式语法。

    【讨论】:

    • 这个问题被标记为嵌入 c,很有可能他根本不在 x86 上工作。 (OTOH,他可能是。)
    【解决方案6】:

    这段代码是直接从"Hacker's Delight" - Figure 7-2 Transposing an 8x8-bit matrix抄来的,我不相信它:

    void transpose8(unsigned char A[8], int m, int n, 
                    unsigned char B[8]) {
       unsigned x, y, t; 
    
       // Load the array and pack it into x and y. 
    
       x = (A[0]<<24)   | (A[m]<<16)   | (A[2*m]<<8) | A[3*m]; 
       y = (A[4*m]<<24) | (A[5*m]<<16) | (A[6*m]<<8) | A[7*m]; 
    
       t = (x ^ (x >> 7)) & 0x00AA00AA;  x = x ^ t ^ (t << 7); 
       t = (y ^ (y >> 7)) & 0x00AA00AA;  y = y ^ t ^ (t << 7); 
    
       t = (x ^ (x >>14)) & 0x0000CCCC;  x = x ^ t ^ (t <<14); 
       t = (y ^ (y >>14)) & 0x0000CCCC;  y = y ^ t ^ (t <<14); 
    
       t = (x & 0xF0F0F0F0) | ((y >> 4) & 0x0F0F0F0F); 
       y = ((x << 4) & 0xF0F0F0F0) | (y & 0x0F0F0F0F); 
       x = t; 
    
       B[0]=x>>24;    B[n]=x>>16;    B[2*n]=x>>8;  B[3*n]=x; 
       B[4*n]=y>>24;  B[5*n]=y>>16;  B[6*n]=y>>8;  B[7*n]=y; 
    }
    

    我没有检查它是否按您需要的方向旋转,如果不是,您可能需要调整代码。

    另外,请记住数据类型和大小 - intunsigned (int) 在您的平台上可能不是 32 位。

    顺便说一句,我怀疑这本书(Hacker's Delight)对于你正在做的工作来说是必不可少的……看看吧,里面有很多很棒的东西。

    【讨论】:

    • +1 我看到的第一个答案与 OP 的问题(嵌入式)有关。 Lisp、x86 asm 和天真的慢如地狱的实现对于嵌入式来说都是毫无用处的......
    • mn 代表什么?
    • @est mn 用于指定当 AB 是较大矩阵时要转置的字节块。如果你只有一个 8 字节数组,mn 都是 1,那么你可以删除它们并稍微简化一下。
    【解决方案7】:

    这类似于get column in a bitboard problem,可以通过将这些输入字节视为 64 位整数的 8 个字节来有效解决。如果位 0 是最低有效位并且字节 0 是数组中的第一个字节,那么我假设您要执行以下操作

    b07 b06 b05 b04 b03 b02 b01 b00      b70 b60 b50 b40 b30 b20 b10 b00
    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
    

    bXY 是字节 X 的位号 Y。屏蔽所有前 7 列并将数组读取为 uint64_t 我们将拥有

    0000000h 0000000g 0000000f 0000000e 0000000d 0000000c 0000000b 0000000a
    

    在小端中,abcdefgh 分别是 b00 到 b70。现在我们只需将该值与幻数 0x2040810204081 相乘,即可在 MSB 中生成一个带有 hgfedcba 的值,这是结果中的翻转形式

    uint8_t get_byte(uint64_t matrix, unsigned col)
    {
        const uint64_t column_mask = 0x8080808080808080ull;
        const uint64_t magic       = 0x2040810204081ull;
    
        return ((matrix << (7 - col)) & column_mask) * magic  >> 56;
    }
    
    // You may need to change the endianness if you address the data in a different way
    uint64_t block8x8 = ((uint64_t)byte[7] << 56) | ((uint64_t)byte[6] << 48)
                      | ((uint64_t)byte[5] << 40) | ((uint64_t)byte[4] << 32)
                      | ((uint64_t)byte[3] << 24) | ((uint64_t)byte[2] << 16)
                      | ((uint64_t)byte[1] <<  8) |  (uint64_t)byte[0];
    
    for (int i = 0; i < 8; i++)
        byte_out[i] = get_byte(block8x8, i);
    

    实际上你应该直接读入一个8字节的数组,这样你以后就不需要组合字节了,但是你需要正确对齐数组

    在 AVX2 中,英特尔为此目的在 BMI2 指令集中引入了 PDEP 指令(可通过 _pext_u64 内在函数访问),因此该功能可以在单个指令中完成

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

    更多转置数组的方法可以在chess programming wiki中找到

    【讨论】:

      猜你喜欢
      • 2012-08-23
      • 2014-01-09
      • 2011-02-09
      • 2019-01-14
      • 2022-12-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-04
      相关资源
      最近更新 更多