2015-04-24 9 views
5

Mit SSE-Intrinsics, habe ich einen Vektor von vier 32-Bit-Floats im Bereich von 0-255 geklammert und auf nächste Ganzzahl gerundet. Ich möchte jetzt diese vier als Bytes schreiben.SSE intrinsics: Konvertieren 32-Bit-Gleitkommazahlen in UNSIGNED 8-Bit-Ganzzahlen

Es ist eine intrinsische _mm_cvtps_pi8, die 32-Bit-8-Bit-unterzeichnet int konvertieren, aber das Problem ist, dass jeder Wert über 127 bis 127 einklemmt kann ich keine Anleitung finden, die klemmt vorzeichenlose 8-Bit-Werte.

Ich habe eine Intuition, was ich tun möchte, ist eine Kombination von _mm_cvtps_pi16 und _mm_shuffle_pi8 gefolgt von der Anweisung, um die vier Bytes, die ich interessiere, in den Speicher zu bekommen. Ist das der beste Weg? Ich werde sehen, ob ich herausfinden kann, wie man die Shuffle-Control-Maske codiert.

UPDATE: Folgendes scheint genau das zu tun, was ich will. Gibt es einen besseren Weg?

#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: Hier ist eine noch bessere Lösung zu Harold Antwort basiert:

#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

Warten Sie, Sie wissen '_mm_shuffle_pi8' ist die mm-Register-Version, oder? Vergiss nicht deine '_mm_empty' – harold

+0

@harold: Oh, guter Punkt. Ich habe jedoch "-mfpmath = sse" in der Compiler-Befehlszeile. –

+0

Darf ich vorschlagen, das '_mm_packus_epi32' durch' _mm_packs_epi32' zu ersetzen? Wie Peter sagte, es funktioniert gut und erfordert nur SSE2. Dein (basierend auf Harolds) benötigt SSE4.1 – user1593842

Antwort

8

Es gibt keine direkte Umwandlung von float zu Byte, _mm_cvtps_pi8 ist ein Verbund. _mm_cvtps_pi16 ist auch eine Zusammensetzung, und in diesem Fall macht es nur einige sinnlose Sachen, die Sie mit dem Mischen rückgängig machen. Sie kehren auch lästige __m64 zurück.

Wie auch immer, wir können in dwords konvertieren (signiert, aber das ist egal), und dann packen (unsigned) oder mischen sie in Bytes. _mm_shuffle_(e)pi8 erzeugt eine pshufb, Core2 45nm und AMD-Prozessoren sind nicht so gern und Sie müssen eine Maske von irgendwo bekommen.

In beiden Fällen müssen Sie nicht zuerst auf die nächste Ganzzahl runden, die Konvertierung wird dies tun. Zumindest, wenn Sie den Rundungsmodus nicht gestört haben.

Verwendung von Packs 1: (nicht getestet) - wahrscheinlich nicht sinnvoll, packusdw gibt bereits vorzeichenlose Wörter aus, aber dann packuswb will wieder signierte Wörter. Herumgehalten, weil anderswo darauf Bezug genommen wird.

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

Mit verschiedenen schlurft:

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

Mit Shuffle: (nicht getestet)

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

Ich mag deine Packungslösung wirklich. Was schön ist, ist, dass das Runden UND das Klemmen automatisch geschieht. Es gibt jedoch einen Eckfall, obwohl ich nicht glaube, dass es mich betrifft: Wenn ich zum Beispiel 100000 in einen der Floats lege, wird es beim ersten Mal auf 65535 geklemmt (nehme ich an). Beim zweiten Mal wird es jedoch als vorzeichenbehafteter Wert (-1) neuinterpretiert und dann vom packuswb auf Null gesetzt. Irgendeine kostengünstige Lösung dafür? –

+0

@TimothyMiller vielleicht, kann ich nicht wirklich etwas schlau denken, nur das offensichtliche "' pminuw' mit 255 " – harold

+0

@TimothyMiller: Ja,' packuswb' behandelt seine Eingabe als signiert, aber als unsigned ausgegeben, so gibt es ein Problem. Sie können 'pand' verwenden, um die geradzahligen Bytes zwischen' packusdw' und 'packuswb' zu maskieren, um das gleiche Ergebnis wie' pminuw' zu erreichen. Oder arbeiten Sie mit Floats im Bereich [-128..127] und konvertieren Sie sie in den Bereich [0..255], wobei "Paddb" ein Vektor von 128s ist. –

4

Wir können, indem Sie die erste Stufe mit signierter Sättigung der Verpackung des unsigned Klemm Problem lösen. [0-255] passt in einen vorzeichenbehafteten 16-Bit-Int, so dass Werte in diesem Bereich nicht geklemmt bleiben. Werte außerhalb dieses Bereichs bleiben auf derselben Seite. Der Schritt signed16 -> unsigned8 klemmt sie also korrekt.

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

Dies erfordert nur SSE2, nicht SSE4.1- für packusdw.

Ich nehme an, das ist der Grund SSE2 nur vorzeichenbehaftet pack von dword zu Wort, aber beide vorzeichenbehaftete und vorzeichenlose pack von Wort zu Byte. packuswd ist nur nützlich, wenn Ihr Endziel uint16_t ist, anstatt weiter zu packen.(Seit dem müssen Sie das Vorzeichen abmasken, bevor Sie es einer weiteren Packung zuführen). Wenn Sie packusdw -> packuswb verwendet haben, erhalten Sie falsche Ergebnisse, wenn der erste Schritt auf uint16_t> 0x7fff gesättigt ist. packuswb würde das als ein negatives int16_t interpretieren und es auf 0 sättigen. packssdw würde solche Eingänge auf 0x7fff sättigen, die max int16_t.

(Wenn Ihr 32-Bit-Eingänge immer < = 0x7fff sind, können Sie entweder, aber SSE4.1 packusdw braucht mehr Befehlsbytes als SSE2 packsswd, und nie schneller läuft.)


Wenn Ihre Quellwerte‘ t negativ sein, und Sie haben nur einen Vektor von 4 Schwimmern, nicht viele, können Sie Harolds pshufb Idee verwenden. Wenn nicht, müssen Sie negative Werte auf Null beschränken, anstatt die unteren Bytes durch Mischen zu ersetzen.

Mit

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

kann als die Verwendung von zwei pack Anweisungen etwas effizienter sein, da pmax auf Port 1 oder 5 (Intel Haswell) laufen kann. cvtps2dq ist nur Port 1, pshufb und pack* sind nur Port 5.

+0

In meinem Fall hatte ich negative Werte, also war der Shuffle von Harold nicht genug. Ihr Shuffle funktioniert, benötigt aber leider SSE4.1 wegen des 'pmaxsd'. Beide SSE4.1-Lösungen (Packs und Suffle) laufen auf meinem i7 980x mit der gleichen Geschwindigkeit. Werde jetzt deine erste Lösung ausprobieren. – user1593842

+0

Ihr erster Vorschlag, mit packssdw, funktioniert gut (verwendet mit Harolds). Jetzt haben wir SSE2 und SSE4.1! (beide laufen auch mit der gleichen Geschwindigkeit) – user1593842