2015-04-24 19 views
5

Utilizzando gli intrinseci SSE, ho ottenuto un vettore di quattro float 32 bit fissati all'intervallo 0-255 e arrotondati al numero intero più vicino. Mi piacerebbe ora scrivere quei quattro come byte.Elementi intrinseci SSE: Converti i float a 32 bit in interi a 8 bit UNSIGNED

C'è un intrinseco _mm_cvtps_pi8 che converte a 32 bit a 8-bit firmato int, ma il problema non è che qualsiasi valore oltre 127 viene fissato al 127. Non riesco a trovare tutte le istruzioni che vi morsetto per valori a 8 bit senza segno.

Ho intuito che quello che potrei voler fare è una combinazione di _mm_cvtps_pi16 e _mm_shuffle_pi8 seguita da istruzioni di spostamento per ottenere i quattro byte che mi interessano nella memoria. È il modo migliore per farlo? Vedrò se riesco a capire come codificare la maschera di controllo shuffle.

AGGIORNAMENTO: Quanto segue sembra fare esattamente ciò che voglio. C'è un modo migliore?

#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; 
} 

UPDATE2: Ecco una soluzione ancora migliore in base alla risposta di Harold:

#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; 
} 
+0

Aspetta, lo sai '_mm_shuffle_pi8' è la versione mm-register, giusto? Non dimenticare il tuo '_mm_empty' – harold

+0

@harold: Oh, buon punto. Tuttavia, ho '-mfpmath = sse' sulla riga di comando del compilatore. –

+0

Posso suggerire di sostituire '_mm_packus_epi32' con' _mm_packs_epi32'? Come ha detto Peter, funziona perfettamente e richiede solo SSE2. Il tuo (basato su harold's) richiede SSE4.1 – user1593842

risposta

8

Non c'è conversione diretta da galleggiante byte, _mm_cvtps_pi8 è un composito. _mm_cvtps_pi16 è anche un composito, e in questo caso si tratta solo di cose inutili che si annullano con lo shuffle. Inoltre restituiscono fastidiosi __m64.

In ogni caso, siamo in grado di convertire in dwords (firmati, ma non importa), quindi pack (senza segno) o shuffle in byte. _mm_shuffle_(e)pi8 genera un pshufb, i processori Core2 45nm e AMD non ne sono troppo entusiasti e devi ottenere una maschera da qualche parte.

In entrambi i casi non è necessario arrotondare prima il numero intero più vicino, il convertitore lo farà. Almeno, se non hai incasinato la modalità di arrotondamento.

Utilizzo dei pacchetti 1: (non testato) - probabilmente non utile, packusdw contiene già parole senza segno, ma quindi packuswb desidera nuovamente le parole firmate. Tenuto in giro perché è riferito altrove.

cvtps2dq xmm0, xmm0 
packusdw xmm0, xmm0  ; unsafe: saturates to a different range than packuswb accepts 
packuswb xmm0, xmm0 
movd somewhere, xmm0 

utilizzando diversi riordini:

cvtps2dq xmm0, xmm0 
packssdw xmm0, xmm0  ; correct: signed saturation on first step to feed packuswb 
packuswb xmm0, xmm0 
movd somewhere, xmm0 

Utilizzando casuale: (non testato)

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 
+1

Mi piace molto la soluzione del tuo pacchetto. La cosa bella è che l'arrotondamento E il serraggio avvengono automaticamente. C'è un caso d'angolo, tuttavia, anche se non penso che mi interessi: se metto 100000 in uno dei float, la prima volta viene bloccato a 65535 (presumo). La seconda volta, tuttavia, viene reinterpretata come valore con segno (-1) e quindi bloccata a zero dal packuswb. Qualche soluzione a basso costo per questo? –

+0

@TimothyMiller forse, non riesco davvero a pensare a qualcosa di intelligente, solo l'ovvio "' pminuw' con 255 " – harold

+0

@TimothyMiller: Sì,' packuswb' tratta il suo input come firmato, ma restituisce come unsigned, quindi c'è un problema. Si potrebbe usare 'pand' per mascherare i byte pari tra' packusdw' e 'packuswb' per ottenere lo stesso risultato di' pminuw'. Oppure lavora con floats nell'intervallo [-128..127] e convertili nell'intervallo [0..255] con 'paddb' un vettore di 128s. –

4

possiamo risolvere il problema di bloccaggio senza segno facendo la prima fase di imballaggio con la saturazione firmato. [0-255] si inserisce in un int firmato a 16 bit, quindi i valori in tale intervallo rimarranno sbloccati. I valori al di fuori di tale intervallo rimarranno dalla stessa parte. Pertanto, il passo firmato16 -> unsigned8 li vincolerà correttamente.

;; 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 

Questo richiede solo SSE2, non SSE4.1 per packusdw.

Suppongo che questo sia il motivo per cui SSE2 ha incluso solo pacchetto firmato da dword a word, ma sia pacchetto firmato che non firmato da parola a byte. packuswd è utile solo se il tuo obiettivo finale è uint16_t, invece di un ulteriore imballaggio.(Da allora avresti bisogno di mascherare il bit del segno prima di alimentarlo con un altro pacchetto).

Se si è utilizzato packusdw -> packuswb, si ottengono risultati fasulli quando il primo passaggio è saturo a uint16_t> 0x7fff. packuswb interpreterà che come valore negativo int16_t e lo saturi a 0. packssdw saturare tali ingressi a 0x7fff, il massimo int16_t.

(Se gli ingressi a 32 bit sono sempre < = 0x7FFF, è possibile utilizzare sia, ma SSE4.1 packusdw richiede più byte di istruzioni di SSE2 packsswd, e mai corre più veloce.)


Se i valori di origine puo' t essere negativo, e hai solo un vettore di 4 galleggianti, non molti, puoi usare l'idea di harold pshufb. In caso contrario, è necessario bloccare i valori negativi a zero anziché troncare il rimescolando i bassi byte in posizione.

Utilizzando

;; 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 

potrebbe essere leggermente più efficiente rispetto all'utilizzo di due pack istruzioni, perché pmax può essere eseguito sulla porta 1 o 5 (Intel Haswell). cvtps2dq è solo la porta 1, pshufb e pack* solo la porta 5.

+0

Nel mio caso ho ottenuto valori negativi, quindi lo shuffle di harold non era abbastanza. Il tuo shuffle funziona, ma purtroppo richiede SSE4.1 a causa del 'pmaxsd'. Entrambe le soluzioni SSE4.1 (pacchetti e suffisso) funzionano alla stessa velocità sul mio i7 980x. Darà la tua prima soluzione una prova ora. – user1593842

+0

Il tuo primo suggerimento, usando packssdw, funziona alla grande (usato con quello di harold). Ora abbiamo SSE2 e SSE4.1! (entrambi funzionano alla stessa velocità) – user1593842

Problemi correlati