Сдвиг SSE/SIMD с размером / гранулярностью однобайтового элемента?
Как вы знаете, у нас есть ниже инструкции Shift в SIMD SSE: PSLL
(WDQ) и PSRL
(WDQ)
Нет никаких PSLLB
инструкция, так как мы можем сдвинуть векторы 8-битных значений (одиночные байты)?
2 ответа
В особом случае сдвига влево на один, вы можете использовать paddb xmm0, xmm0
,
Как отмечает в комментариях Шут, лучший вариант подражать несуществующему psrlb
а также psllb
использовать более широкий сдвиг, а затем маскировать любые биты, которые пересекают границы элементов.
например
psrlw xmm0, 2 ; doesn't matter what size (w/d/q): performance is the same for all sizes on all CPUs
pand xmm0, [mask_right2]
section .rodata
align 16
;; required mask depends on the shift count
mask_right2: times 16 db 0xff >> 2 (16 bytes of 0x3f)
Или передайте 0x3f в векторный регистр перед циклом другим способом, например vpbroadcastd
или же vbroadcastss
из мечты в памяти, SSE3 movddup
от слова или просто movdqa
векторная нагрузка (vpbroadcastb
требует дополнительной ALU UOP, в отличие от dword или более широких трансляций, которые просто загружаются). Или генерировать на лету с такой последовательностью, как pcmpeqd xmm0,xmm0
/ psrlw xmm0, 8+2
/ packuswb xmm0,xmm0
, При правильном выборе количества сдвигов вы можете создать любой шаблон из 2 n -1 байтов (повторяющиеся нули, а затем повторяющиеся).
mov r32, imm32
/ movd xmm, r32
и shuffle также вариант, но, вероятно, не будет сохранять байты инструкции по сравнению с pcmpeqw
/... последовательность. (Обратите внимание, что версия реестра-источника VBROADCASTSS
только для AVX2, что здесь не имеет значения, поскольку целочисленные сдвиги 256b также только для AVX2.)
Для сдвига вектора с переменным счетом создание маски в целочисленном регистре и передача ее в вектор является одним из вариантов (используйте pshufb
с нулевым регистром для передачи младшего байта или использования imul eax, eax, 0x01010101
перейти от байта к мечту movd
+ pshufd
). Вы также можете использовать pcmpeqd
метод для создания единого вектора и использования psrlw xmm0, xmm1
а потом pack
или же pshufb
,
Я не вижу подобного эффективного способа эмулирования арифметического сдвига вправо (несуществующего PSRAB
). Старший байт каждого слова обрабатывается правильно PSRAW
, Смещение младшего байта каждого слова в верхнюю позицию позволит другому PSRAW
скопируйте его бит знака столько раз, сколько требуется.
; input in xmm0. Using AVX to save on mov instructions
VPSLLDQ xmm1, xmm0, 1 ; or VPSLLW xmm1, xmm0, 8, but this distributes one of the uops to the shuffle port
VPSRAW xmm1, xmm1, 8+2 ; shift low bytes back to final destination
VPSRAW xmm0, xmm0, 2 ; shift high bytes, leaving garbage in low bytes
VPBLENDVB xmm0, xmm1, xmm0, xmm2 ; (where xmm2 holds a mask of alternating 0 and -1, which could be generated with pcmpeqw / psrlw 8). This insn is fairly slow
Не существует непосредственного смешения с гранулярностью байтов, поскольку один непосредственный байт может кодировать только 8 элементов.
Без VPBLENDVB (возможно, даже лучше, когда он доступен, если генерирование или загрузка константы для него происходит медленно):
VPSLLDQ xmm1, xmm0, 1 ; or VPSLLW 8
VPSRAW xmm1, xmm1, n ; low bytes in the wrong place
VPSRAW xmm0, xmm0, 8+n ; shift high bytes all the way to the bottom of the element
VPSLLW xmm0, xmm0, 8 ; high bytes back in place, with zero in the low byte. (VPSLLDQ can't work: PSRAW 8+n leaves garbage we need to clear)
VPSRLW xmm1, xmm1, 8 ; shift low bytes into place, leaving zero in the high byte. (VPSRLDQ 1 could do this, if we started with VPSLLW instead of VPSLLDQ)
VPOR xmm0, xmm0, xmm1
Использование PAND/PANDN/POR с константой (чередование 0/-1 байтов) в регистре также будет работать (с гораздо меньшим давлением на порт сдвига) для выполнения смешивания байтов, и является лучшим выбором, если вам нужно сделать это в цикле.
Вот еще один способ эмулировать "psrab", который работает для SSE или AVX с 1 пустым регистром:
__ punpckhbw(scratch, src); // junk in low bytes
__ punpcklbw(dst, src); // junk in low bytes
__ psraw(scratch, 8 + shift);
__ psraw(dst, 8 + shift);
__ packsswb(dst, scratch); // pack words to get result