Встроенные функции 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

Другие вопросы по тегам