2012-04-16 12 views
6

Ich lerne SIMD-Fähigkeiten zu nutzen, indem ich meine persönliche Bildverarbeitungsbibliothek mit Vektor-Intrinsics umschreibe. Eine Grundfunktion ist eine einfache "array +=", dhSIMD-Array für beliebige Array-Längen hinzufügen

void arrayAdd(unsigned char* A, unsigned char* B, size_t n) { 
    for(size_t i=0; i < n; i++) { B[i] += A[i] }; 
} 

Für beliebige Array Längen, die offensichtliche Code SIMD (um 16 ausgerichtet angenommen wird) ist so etwas wie:

size_t i = 0; 
__m128i xmm0, xmm1; 
n16 = n - (n % 16); 
for (; i < n16; i+=16) { 
    xmm0 = _mm_load_si128((__m128i*) (A + i)); 
    xmm1 = _mm_load_si128((__m128i*) (B + i)); 
    xmm1 = _mm_add_epi8(xmm0, xmm1); 
    _mm_store_si128((__m128i*) (B + i), xmm1); 
} 
for (; i < n; i++) { B[i] += A[i]; } 

Aber ist es möglich, tun alle die Ergänzungen mit SIMD Anweisungen? Ich dachte daran, dies zu versuchen:

__m128i mask = (0x100<<8*(n - n16))-1; 
_mm_maskmoveu_si128(xmm1, mask, (__m128i*) (B + i)); 

für die zusätzlichen Elemente, aber wird das zu undefiniertem Verhalten führen? Die mask sollte garantieren, dass kein Zugriff über die Array-Grenzen hinaus erfolgt (glaube ich). Die Alternative besteht darin, die zusätzlichen Elemente zuerst auszuführen, aber dann muss das Array um n-n16 ausgerichtet werden, was nicht richtig erscheint.

Gibt es ein anderes, optimaleres Muster wie vektorisierte Schleifen?

+0

Sie, dass die Feldlängen immer ein Vielfaches von 16 Bytes im Code sicherstellen könnten (obwohl möglicherweise weniger Elemente tatsächlich verwendet werden), so dass dieser Epilog nie kommt. Aber der Epilog ist in Bezug auf die Geschwindigkeit wirklich nicht wichtig. – Walter

Antwort

4

Eine Option besteht darin, Ihr Array auf ein Vielfaches von 16 Bytes aufzufüllen. Dann können Sie 128 Bit laden/hinzufügen/speichern und ignorieren Sie einfach die Ergebnisse nach dem Punkt, den Sie interessieren.

Für große Arrays, obwohl der Overhead des byteweise "Epilog" wird sehr klein sein. Abrollen kann die Schleife der Leistung verbessern mehr, so etwas wie:

for (; i < n32; i+=32) { 
    xmm0 = _mm_load_si128((__m128i*) (A + i)); 
    xmm1 = _mm_load_si128((__m128i*) (B + i)); 
    xmm2 = _mm_load_si128((__m128i*) (A + i + 16)); 
    xmm3 = _mm_load_si128((__m128i*) (B + i + 16)); 
    xmm1 = _mm_add_epi8(xmm0, xmm1); 
    xmm3 = _mm_add_epi8(xmm2, xmm3); 
    _mm_store_si128((__m128i*) (B + i), xmm1); 
    _mm_store_si128((__m128i*) (B + i + 16), xmm3); 
} 
// Do another 128 bit load/add/store here if required 

Aber es ist schwer, ohne zu sagen, etwas Profilierung zu tun.

Sie könnten auch eine nicht ausgerichtete Lade/Speicher am Ende tun (vorausgesetzt, Sie haben mehr als 16 Bytes), obwohl dies wahrscheinlich keinen großen Unterschied machen wird. Z.B. wenn Sie 20 Bytes haben Sie eine Lade-/Speicher auf Offset 0 und eine andere unaligned Last/add/store (_mm_storeu_si128, __mm_loadu_si128) 4.

Sie ausgleichen _mm_maskmoveu_si128 verwenden könnte, aber Sie müssen die Maske in einem XMM-Register erhalten , und Ihr Beispielcode wird nicht funktionieren. Sie möchten wahrscheinlich das Maskenregister auf alle FFs setzen und dann eine Verschiebung verwenden, um es auszurichten. Am Ende des Tages wird es wahrscheinlich langsamer als die unausgerichtete load/add/store.

Das wäre so etwas wie:

mask = _mm_cmpeq_epi8(mask, mask); // Set to all FF's 
mask = _mm_srli_si128(mask, 16-(n%16)); // Align mask 
_mm_maskmoveu_si128(xmm, mask, A + i); 
+0

In der Praxis würde ich die Masken in eine Nachschlagetabelle legen. Denkst du, es wäre immer noch langsamer als die "Epilog" -Schleife? –

+0

@reve_etrange: Wahrscheinlich nicht langsamer, aber es ist schwierig zu wissen, ohne die beiden Lösungen zu messen. Versuche es. –

+0

Ich werde es versuchen. Aber ist es ein legaler Speicherzugriff? Da * ein * Wert von 'mask' eine Array-Grenze verletzen könnte. –