2012-06-07 12 views
11

Ich versuche, Summenreduktion von 32 Elementen (jeweils 1 Byte Daten) auf einem Intel i3-Prozessor zu finden. Ich tat das:Summenreduzierung von vorzeichenlosen Bytes ohne Überlauf, mit SSE2 auf Intel

Allerdings nimmt es mehr Zeit, da meine Anwendung eine Echtzeit-Anwendung viel weniger Zeit benötigt. Bitte beachten Sie, dass die endgültige Summe könnte mehr als 255 sein.

Gibt es eine Möglichkeit, die ich mit Low-Level-SIMD SSE2-Anweisungen implementieren kann? Leider habe ich SSE nie benutzt. Ich habe versucht, zu diesem Zweck nach der Funktion sse2 zu suchen, aber es ist auch nicht verfügbar. Ist es (s) garantiert, die Berechnungszeit für solch kleine Probleme zu reduzieren?

Irgendwelche Vorschläge ??

Hinweis: Ich habe die ähnlichen Algorithmen mit OpenCL und CUDA implementiert und das funktionierte großartig, aber nur, wenn die Problemgröße groß war. Bei kleinen Problemen waren die Kosten für den Overhead höher. Nicht sicher, wie es auf SSE funktioniert

+0

Ist die Summe größer als 255? – hirschhornsalz

+0

Ja, die endgültige Summe könnte größer als 255 sein – gpuguy

Antwort

7

Sie können PSADBW missbrauchen, um kleine horizontale Summen schnell zu berechnen.

Etwas wie folgt aus: (nicht getestet)

pxor xmm0, xmm0 
psadbw xmm0, [a + 0] 
pxor xmm1, xmm1 
psadbw xmm1, [a + 16] 
paddw xmm0, xmm1 
pshufd xmm1, xmm0, 2 
paddw xmm0, xmm1 ; low word in xmm0 is the total sum 

Versuchte intrinsics Version:

ich nie intrinsics verwenden sodass dieser Code wahrscheinlich überhaupt keinen Sinn macht. Die Demontage sah allerdings gut aus.

uint16_t sum_32(const uint8_t a[32]) 
{ 
    __m128i zero = _mm_xor_si128(zero, zero); 
    __m128i sum0 = _mm_sad_epu8(
         zero, 
         _mm_load_si128(reinterpret_cast<const __m128i*>(a))); 
    __m128i sum1 = _mm_sad_epu8(
         zero, 
         _mm_load_si128(reinterpret_cast<const __m128i*>(&a[16]))); 
    __m128i sum2 = _mm_add_epi16(sum0, sum1); 
    __m128i totalsum = _mm_add_epi16(sum2, _mm_shuffle_epi32(sum2, 2)); 
    return totalsum.m128i_u16[0]; 
} 
+0

Könnten Sie bitte die Intel® C++ Compiler Intrinsic Equivalents für die oben genannten schreiben? – gpuguy

+0

@gpuguy Ich habe versucht, aber ich benutze nie intrinsics, also habe ich wahrscheinlich etwas vermasselt. Das 'reininterpret_cast' sieht auch nicht gut aus, aber ich konnte nicht herausfinden, wie ich es loswerden könnte. – harold

+0

Um denselben Trick für "int8_t" (anstelle von "uint8_t") zu verwenden: verschiebt den Bereich auf unsigned (xor mit 0x80), dann subtrahiere "16 * 0x80" von der Summe. Ein Beispiel mit intrinsischen Eigenschaften finden Sie in [dieses Patch für die Vektorklassenbibliothek von Agner Fog] (https://github.com/pcodes/vectorclass/commit/630ca802bb1abefd096907f8457d090c28c8327b). Die gleiche Idee funktioniert für [ein AVX2-Ymm-Vektor] (https://github.com/pcodes/vectorclass/commit/11aa77071d25d7d93090789006250f8992f44272)). –

5

Das ist ein bisschen langatmig, aber es sollte noch mindestens 2x schneller als der Skalar-Code sein:

uint16_t sum_32(const uint8_t a[32]) 
{ 
    const __m128i vk0 = _mm_set1_epi8(0); // constant vector of all 0s for use with _mm_unpacklo_epi8/_mm_unpackhi_epi8 
    __m128i v = _mm_load_si128(a);   // load first vector of 8 bit values 
    __m128i vl = _mm_unpacklo_epi8(v, vk0); // unpack to two vectors of 16 bit values 
    __m128i vh = _mm_unpackhi_epi8(v, vk0); 
    __m128i vsum = _mm_add_epi16(vl, vh); 
    v = _mm_load_si128(&a[16]);    // load second vector of 8 bit values 
    vl = _mm_unpacklo_epi8(v, vk0);   // unpack to two vectors of 16 bit values 
    vh = _mm_unpackhi_epi8(v, vk0); 
    vsum = _mm_add_epi16(vsum, vl); 
    vsum = _mm_add_epi16(vsum, vh); 
    // horizontal sum 
    vsum = _mm_add_epi16(vsum, _mm_srli_si128(vsum, 8)); 
    vsum = _mm_add_epi16(vsum, _mm_srli_si128(vsum, 4)); 
    vsum = _mm_add_epi16(vsum, _mm_srli_si128(vsum, 2)); 
    return _mm_extract_epi16(vsum, 0); 
} 

Beachten Sie, dass a[] Bedürfnisse 16 Byte ausgerichtet sein.

Sie können wahrscheinlich den obigen Code mit _mm_hadd_epi16 verbessern.

+0

Wie stelle ich sicher, dass a [] 16 Byte ausgerichtet ist? Gibt es in SSE etwas Ähnliches wie __align __ (16) in CUDA? – gpuguy

+0

Es hängt davon ab, welchen Compiler und welches Betriebssystem Sie verwenden - z. Für gcc mit nicht-dynamischen Zuweisungen verwenden Sie '__attribute__ ((aligned (16)))' - für dynamische Zuweisungen unter Linux verwenden Sie 'memalign()' oder 'posix_memalign()'. –

+0

werde dies ablehnen müssen; es funktioniert, aber 'psadbw' ist die richtige Antwort. Für vorzeichenbehaftete 'int8_t' können Sie mit' xor' in den Bereich vorzeichenlos schalten, um jedem Byte 0x80 hinzuzufügen, und vom Ergebnis '16 * 0x80' subtrahieren. (Siehe [Dieser Patch für die Vektorklassenbibliothek von Agner Fog] (https: // github.com/pcodes/vectorclass/commit/630ca802bb1abefd096907f8457d090c28c8327b) zum Beispiel mit intrinsics. Die gleiche Idee funktioniert für [ein AVX2-Ymm-Vektor] (https://github.com/pcodes/vectorclass/commit/11aa77071d25d7d93090789006250f8992f44272)). Aber das OP hier scheint bereits nicht signiert zu haben, also ist psadbw ein großer Gewinn. –