8

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

例如,当我完成时:

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

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

4

7 回答 7

16

这段代码直接来自“Hacker's Delight”——图 7-2 转置一个 8x8 位矩阵,我不相信它:

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)对于你正在做的工作来说是必不可少的……看看吧,里面有很多很棒的东西。

于 2011-08-03T19:44:38.520 回答
5

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

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

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

如何利用 SSE2 在程序集中转置位矩阵。

于 2011-08-03T17:51:19.800 回答
3

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

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

于 2011-08-03T17:39:28.343 回答
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 
于 2011-08-03T17:44:21.480 回答
1

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

于 2011-08-03T17:40:13.397 回答
1

如果您想要一个优化的解决方案,您将使用 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 样式语法。

于 2011-08-03T19:21:38.347 回答
1

这类似于位板问题中的 get 列,可以通过将这些输入字节视为 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

在 little endian 中,abcdefgh分别是 b00 到 b70。现在我们只需将该值乘以幻数 0x2040810204081 以hgfedcba在 MSB 中生成一个值,这是结果中的翻转形式

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

更多转置数组的方法可以在国际象棋编程维基中找到

于 2018-08-02T12:02:22.797 回答