Встроенные функции SSE: преобразование 32-разрядных чисел в число 8-разрядных целых чисел без знака
Используя встроенные функции SSE, я получил вектор из четырех 32-битных чисел с ограничением в диапазоне от 0 до 255 и округленный до ближайшего целого числа. Теперь я хотел бы записать эти четыре в байтах.
Есть свойственный _mm_cvtps_pi8
это преобразует 32-разрядное в 8-разрядное целое число со знаком, но проблема в том, что любое значение, превышающее 127, ограничивается значением 127. Я не могу найти никаких инструкций, которые бы ограничивали 8-разрядные значения без знака.
У меня есть интуиция, что я могу захотеть сделать это _mm_cvtps_pi16
а также _mm_shuffle_pi8
затем инструкция перемещения, чтобы получить четыре байта, которые мне нужны, в память. Это лучший способ сделать это? Я собираюсь посмотреть, смогу ли я выяснить, как кодировать маску управления перемешиванием.
ОБНОВЛЕНИЕ: следующее, кажется, делает именно то, что я хочу. Есть ли способ лучше?
#include <tmmintrin.h>
#include <stdio.h>
unsigned char out[8];
unsigned char shuf[8] = { 0, 2, 4, 6, 128, 128, 128, 128 };
float ins[4] = {500, 0, 120, 240};
int main()
{
__m128 x = _mm_load_ps(ins); // Load the floats
__m64 y = _mm_cvtps_pi16(x); // Convert them to 16-bit ints
__m64 sh = *(__m64*)shuf; // Get the shuffle mask into a register
y = _mm_shuffle_pi8(y, sh); // Shuffle the lower byte of each into the first four bytes
*(int*)out = _mm_cvtsi64_si32(y); // Store the lower 32 bits
printf("%d\n", out[0]);
printf("%d\n", out[1]);
printf("%d\n", out[2]);
printf("%d\n", out[3]);
return 0;
}
ОБНОВЛЕНИЕ 2: Вот еще лучшее решение, основанное на ответе Гарольда:
#include <smmintrin.h>
#include <stdio.h>
unsigned char out[8];
float ins[4] = {10.4, 10.6, 120, 100000};
int main()
{
__m128 x = _mm_load_ps(ins); // Load the floats
__m128i y = _mm_cvtps_epi32(x); // Convert them to 32-bit ints
y = _mm_packus_epi32(y, y); // Pack down to 16 bits
y = _mm_packus_epi16(y, y); // Pack down to 8 bits
*(int*)out = _mm_cvtsi128_si32(y); // Store the lower 32 bits
printf("%d\n", out[0]);
printf("%d\n", out[1]);
printf("%d\n", out[2]);
printf("%d\n", out[3]);
return 0;
}
2 ответа
Там нет прямого преобразования из числа с плавающей запятой в байт, _mm_cvtps_pi8
это композит. _mm_cvtps_pi16
это тоже композит, и в этом случае он просто делает бессмысленные вещи, которые вы отменяете с помощью перемешивания. Они также возвращают раздражающие __m64
"S.
В любом случае, мы можем преобразовать в слова (подписанные, но это не имеет значения), а затем упаковать (без знака) или перемешать их в байты. _mm_shuffle_(e)pi8
генерирует pshufb
Процессоры Core2 45nm и AMD не слишком любят это, и вам нужно где-то получить маску.
В любом случае вам не нужно сначала округлять до ближайшего целого числа, конверт сделает это. По крайней мере, если вы не перепутали с режимом округления.
Использование пакетов 1: (не проверено) - вероятно, не полезно, packusdw
уже выводит без знака слова, но потом packuswb
снова хочет подписать слова Хранится вокруг, потому что это упоминается в другом месте.
cvtps2dq xmm0, xmm0
packusdw xmm0, xmm0 ; unsafe: saturates to a different range than packuswb accepts
packuswb xmm0, xmm0
movd somewhere, xmm0
Использование разных тасовок:
cvtps2dq xmm0, xmm0
packssdw xmm0, xmm0 ; correct: signed saturation on first step to feed packuswb
packuswb xmm0, xmm0
movd somewhere, xmm0
Использование shuffle: (не проверено)
cvtps2dq xmm0, xmm0
pshufb xmm0, [shufmask]
movd somewhere, xmm0
shufmask: db 0, 4, 8, 12, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h, 80h
Мы можем решить проблему беззнакового зажима, выполнив первый этап упаковки со знаком насыщения. [0-255]
вписывается в 16-разрядное целое число со знаком, поэтому значения в этом диапазоне останутся свободными. Значения вне этого диапазона останутся с той же стороны. Таким образом, шаг sign16 -> unsigned8 будет корректно фиксировать их.
;; SSE2: good for arrays of inputs
cvtps2dq xmm0, [rsi] ; 4 floats
cvtps2dq xmm1, [rsi+16] ; 4 more floats
packssdw xmm0, xmm1 ; 8 int16_t
cvtps2dq xmm1, [rsi+32]
cvtps2dq xmm2, [rsi+48]
packssdw xmm1, xmm2 ; 8 more int16_t
; signed because that's how packuswb treats its input
packuswb xmm0, xmm1 ; 16 uint8_t
movdqa [rdi], xmm0
Для этого требуется только SSE2, а не SSE4.1 для packusdw
,
Я предполагаю, что это причина, по которой SSE2 включал только подписанный пакет от слова к слову, но и подписанный, и неподписанный пакет от слова к байту. packuswd
полезно только если ваша конечная цель uint16_t
, а не дальнейшая упаковка. (С тех пор вам нужно будет маскировать бит знака перед подачей его в следующую пачку).
Если вы использовали packusdw -> packuswb
, вы получите фиктивные результаты, когда первый шаг насыщен до uint16_t
> 0x7fff. packuswb
будет интерпретировать это как отрицательный int16_t
и насытить его до 0. packssdw
будет насыщать такие входы в 0x7fff
, макс int16_t
,
(Если ваши 32-битные входы всегда <= 0x7fff, вы можете использовать любой из них, но SSE4.1 packusdw
занимает больше инструктивных байтов, чем SSE2 packsswd
и никогда не бежит быстрее.)
Если ваши исходные значения не могут быть отрицательными, и у вас есть только один вектор из 4 чисел с плавающей запятой, а не много, вы можете использовать гарольды pshufb
идея. Если нет, вам нужно ограничить отрицательные значения до нуля, а не обрезать, перетасовывая младшие байты на место.
С помощью
;; SSE4.1, good for a single vector. Use the PACK version above for arrays
cvtps2dq xmm0, xmm0
pmaxsd xmm0, zeroed-register
pshufb xmm0, [mask]
movd [somewhere], xmm0
может быть немного более эффективным, чем использование двух pack
инструкции, потому что pmax
может работать на порте 1 или 5 (Intel Haswell). cvtps2dq
только порт 1, pshufb
а также pack*
только порт 5