Как эффективно преобразовать 8-битное растровое изображение в массив целых чисел 0/1 с x86 SIMD - PullRequest
0 голосов
/ 30 августа 2018

Я хочу преобразовать 8-битное целое число в массив размера 8, каждое значение которого содержит битовое значение целого числа.

Например: у меня есть int8_t x = 8; Я хочу преобразовать это в int8_t array_x = {0,0,0,0,1,0,0,0};

Это должно быть сделано эффективно, так как этот расчет является частью блока обработки сигналов. Есть ли эффективный способ сделать это? Я проверил смесь инструкции. Это не отвечало моим требованиям, когда элементы массива имели размер 8 бит. Платформа разработки - AMD Ryzen.

Ответы [ 2 ]

0 голосов
/ 31 августа 2018

«Маска обратного хода» для одного байта с результатами в формате 0x00:0x01, с SIMD, но без BMI2.

__m128i v = _mm_set1_epi8(bitmap); 
v = _mm_and_si128(v, _mm_set_epi32(0, 0, 0x80402010, 0x08040201));
v = _mm_min_epu8(v, _mm_set1_epi8(1));
_mm_storel_epi64((__m128i*)&array_x[0], v); 
0 голосов
/ 30 августа 2018

Первый пример в конце этого ответа показывает, как использовать инструкцию BMI2 pdep для вычисления массива из 8 байтов.

Обратите внимание, что на процессорах Intel Haswell и новее пропускная способность pdep равна единице. инструкция за цикл и задержка 3 цикла, что быстро. На AMD Ryzen эта инструкция есть к сожалению, относительно медленно: и задержка, и пропускная способность составляют 18 циклов. Для AMD Ryzen лучше заменить инструкцию pdep на умножение и несколько побитовых операций, которые выполняются на AMD Ryzen довольно быстро, см. Второй пример в конце этого ответа.

<Ч />

См. Также здесь и здесь для эффективных вычислений обратной маски движения со скалярным источником и 256-битный AVX2-вектор назначения.

Вместо того, чтобы работать с 8 битами и 8 байтами одновременно, это может быть более эффективно реорганизовать ваш алгоритм для работы с 4 x 8 битами и 4 x 8 байтами на шаг. В этом случае может использоваться полная ширина вектора AVx2 256 бит, что может быть быстрее.

Питер Кордес показывает , что инструкция pext может использоваться для преобразования в обратное направление: от 8 байтов до 8 бит.

<Ч />

Пример кода с инструкцией pdep:

/*  gcc -O3 -Wall -m64 -march=skylake bytetoarr.c  */

#include<stdint.h>
#include<stdio.h>
#include<x86intrin.h>

int main(){

    int i;
    union {
        uint8_t  a8[8];
        uint64_t a64;
    } t;
    /*  With mask = 0b0000000100......0100000001 = 0x0101010101010101    */
    /*  the input bits 0, 1, ..., 7 are expanded                         */
    /*  to the right positions of the uint64_t = 8 x uint8_t output      */
    uint64_t mask = 0x0101010101010101;

    /* example input: */
    uint8_t x = 0b01001100;

    t.a64 = _pdep_u64(x,mask);

    for (i = 0; i < 8; i++){
        printf("a[%i] = %hhu\n", i, t.a8[i]);
    }
}

Вывод:

$ ./a.out
a[0] = 0
a[1] = 0
a[2] = 1
a[3] = 1
a[4] = 0
a[5] = 0
a[6] = 1
a[7] = 0
<Ч />

Пример кода для процессоров AMD Ryzen:

/*  gcc -O3 -Wall -m64 -march=skylake bytetoarr_amd.c  */

#include<stdint.h>
#include<stdio.h>
#include<x86intrin.h>

int main(){

    int i;
    union {
        uint8_t  a8[8];
        uint64_t a64;
    } t;

    /* example input: */
    uint8_t  x    = 0b01001100;

    uint64_t x64  = x;                 
    uint64_t x_hi = x64 & 0xFE;                                                  /* Unset the lowest bit.                        */
    uint64_t r_hi = x_hi * 0b10000001000000100000010000001000000100000010000000; /* Copy the remaining 7 bits 7 times.           */
    uint64_t r    = r_hi | x64;                                                  /* Merge the lowest bit into the result.        */
             t.a64= r & 0x0101010101010101   ;                                   /* Mask off the bits at the unwanted positions. */

    for (i = 0; i < 8; i++){
        printf("a[%i] = %hhu\n", i, t.a8[i]);
    }
}
...