Какой самый быстрый способ поворота бит в блоке 8x8 на биты? - PullRequest
8 голосов
/ 03 августа 2011

Я не уверен, точный термин для того, что я пытаюсь сделать.У меня есть 8x8 блок bits, сохраненный в 8 bytes, каждый байт хранит одну строку.Когда я закончу, я бы хотел, чтобы каждый байт сохранял один столбец.

Например, когда я закончу:

Byte0out = Byte0inBit0 + Byte1inBit0 + Byte2inBit0 + Byte3inBit0 + ...
Byte1out = Byte0inBit1 + Byte1inBit1 + Byte2inBit1 + Byte3inBit1 + ...

Каков самый простой способ сделать это в C , который хорошо работает?

Ответы [ 7 ]

15 голосов
/ 03 августа 2011

Этот код взломан непосредственно из "Восторг хакера" - рисунок 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; 
}

Я не проверял, вращается ли это в нужном вам направлении, в противном случае вам может потребоваться изменить код.

Кроме того, имейте в виду, что типы данных и размеры - int & unsigned (int) могут быть не 32-битными на вашей платформе.

Кстати, я подозреваю, что книга (Восхищение Хакера) важна для той работы, которую вы делаете ... посмотрите, там много отличного.

5 голосов
/ 03 августа 2011

Если вы ищете простейшее решение:

/* 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.

3 голосов
/ 03 августа 2011

Прототип 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)))

Теперь я действительно хочу увидеть, как мой код сравнивается с 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 мс.Это намного быстрее, чем мой неоптимизированный Лисп.

К сожалению, код 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 
2 голосов
/ 03 августа 2011

Это звучит очень похоже на так называемую подпрограмму «от коренастых до плоских», используемую на дисплеях, использующих битовые плоскости.Следующая ссылка использует ассемблер MC68K для своего кода, но предоставляет хороший обзор проблемы (если я правильно понял вопрос):

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

1 голос
/ 02 августа 2018

Это похоже на столбец get в проблеме с битовой доской и может быть эффективно решено, если рассматривать эти входные байты как 8 байтов 64-битного целого числа. Если бит 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, чтобы получить значение с 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 Intel ввела инструкцию PDEP (доступную через встроенную функцию _pext_u64) в наборе команд BMI2 , чтобы эту функцию можно было выполнить в одной инструкции

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

Больше способов транспонировать массив можно найти в шахматной вики по программированию

1 голос
/ 03 августа 2011

Если бы вы хотели оптимизированное решение, вы бы использовали расширения SSE в x86.Вам необходимо использовать 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 итерациями.Извините, нотация не является синтаксисом стиля ATT, который принимают компиляторы c / c ++.

1 голос
/ 03 августа 2011

Вы действительно хотите сделать что-то подобное с инструкциями SIMD с чем-то вроде векторной поддержки GCC: http://ds9a.nl/gcc-simd/example.html

...