Самый быстрый способ распаковать 32 бита в 32-байтовый SIMD-вектор
имея 32 бита, хранящиеся в uint32_t
в памяти, каков самый быстрый способ распаковать каждый бит в отдельный байтовый элемент регистра AVX? Биты могут находиться в любом положении в пределах их соответствующего байта.
Edit: чтобы уточнить, я имею в виду, что бит 0 переходит в байт 0, бит 1 в байт 1. Очевидно, все остальные биты внутри байта на нуле. Лучшее, что я мог на данный момент, это 2 PSHUFB
и иметь регистр маски для каждой позиции.
если uint32_t
- растровое изображение, затем соответствующее векторные элементы должны быть 0 или не 0. (т. е. чтобы мы могли получить векторную маску с vpcmpeqb
против вектора all-zero).
1 ответов
"транслировать" 32 бита 32-разрядного целого числа x
до 32 байт 256-битного регистра YMM z
или 16 байт двух 128-битных регистров XMM z_low
и z_high
вы можете сделать следующее.
С поддержкой AVX2:
__m256i y = _mm256_set1_epi32(x);
__m256i z = _mm256_shuffle_epi8(y,mask1);
z = _mm256_and_si256(z,mask2);
без AVX2 лучше всего сделать это с помощью SSE:
__m128i y = _mm_set1_epi32(x);
__m128i z_low = _mm_shuffle_epi8(y,mask_low);
__m128i z_high = _mm_shuffle_epi8(y,mask_high);
z_low = _mm_and_si128(z_low ,mask2);
z_high = _mm_and_si128(z_high,mask2);
маски и рабочий пример показаны ниже. Если вы планируете сделать это несколько раз, вы, вероятно, следует определение масок за пределами main петля.
#include <immintrin.h>
#include <stdio.h>
int main() {
int x = 0x87654321;
static const char mask1a[32] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01,
0x02, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x02,
0x03, 0x03, 0x03, 0x03,
0x03, 0x03, 0x03, 0x03
};
static const char mask2a[32] = {
0x01, 0x02, 0x04, 0x08,
0x10, 0x20, 0x40, 0x80,
0x01, 0x02, 0x04, 0x08,
0x10, 0x20, 0x40, 0x80,
0x01, 0x02, 0x04, 0x08,
0x10, 0x20, 0x40, 0x80,
0x01, 0x02, 0x04, 0x08,
0x10, 0x20, 0x40, 0x80,
};
char out[32];
#if defined ( __AVX2__ )
__m256i mask2 = _mm256_loadu_si256((__m256i*)mask2a);
__m256i mask1 = _mm256_loadu_si256((__m256i*)mask1a);
__m256i y = _mm256_set1_epi32(x);
__m256i z = _mm256_shuffle_epi8(y,mask1);
z = _mm256_and_si256(z,mask2);
_mm256_storeu_si256((__m256i*)out,z);
#else
__m128i mask2 = _mm_loadu_si128((__m128i*)mask2a);
__m128i mask_low = _mm_loadu_si128((__m128i*)&mask1a[ 0]);
__m128i mask_high = _mm_loadu_si128((__m128i*)&mask1a[16]);
__m128i y = _mm_set1_epi32(x);
__m128i z_low = _mm_shuffle_epi8(y,mask_low);
__m128i z_high = _mm_shuffle_epi8(y,mask_high);
z_low = _mm_and_si128(z_low,mask2);
z_high = _mm_and_si128(z_high,mask2);
_mm_storeu_si128((__m128i*)&out[ 0],z_low);
_mm_storeu_si128((__m128i*)&out[16],z_high);
#endif
for(int i=0; i<8; i++) {
for(int j=0; j<4; j++) {
printf("%x ", out[4*i+j]);
}printf("\n");
} printf("\n");
}
чтобы получить 0 или -1 в каждый элемент вектора:
это занимает один дополнительный шаг _mm256_cmpeq_epi8
против всех-нулями. Любое ненулевое значение превращается в 0, а ноль - в -1. Если мы не хотим этой инверсии, используйте andnot
вместо and
. Он инвертирует свой первый операнд.
__m256i expand_bits_to_bytes(uint32_t x)
{
__m256i xbcast = _mm256_set1_epi32(x); // we only use the low 32bits of each lane, but this is fine with AVX2
// Each byte gets the source byte containing the corresponding bit
__m256i shufmask = _mm256_set_epi64x(
0x0303030303030303, 0x0202020202020202,
0x0101010101010101, 0x0000000000000000);
__m256i shuf = _mm256_shuffle_epi8(xbcast, shufmask);
__m256i andmask = _mm256_set1_epi64x(0x8040201008040201); // every 8 bits -> 8 bytes, pattern repeats.
__m256i isolated_inverted = _mm256_andnot_si256(shuf, andmask);
// this is the extra step: compare each byte == 0 to produce 0 or -1
return _mm256_cmpeq_epi8(isolated_inverted, _mm256_setzero_si256());
}
смотрите на Проводник Компилятора Godbolt.