2016-03-24 2 views
1

Ich weiß, wie man einen __m256 summiert, um einen einzigen summierten Wert zu erhalten. Ich habe jedoch 8 Vektoren wie EingangDer effizienteste Weg, um ein __m256 von horizontalen Summen von 8 Quelle __m256 Vektoren zu erhalten

1: a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], 
....., 
....., 
8: h[0], h[1], h[2], h[3], h[4], a[5], a[6], a[7] 

Output

a[0]+a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7], 
...., 
h[0]+h[1]+h[2]+h[3]+h[4]+h[5]+h[6]+h[7] 

My Methode. Neugierig, wenn es einen besseren Weg gibt.

  __m256 sumab = _mm256_hadd_ps(accumulator1, accumulator2); 
      __m256 sumcd = _mm256_hadd_ps(accumulator3, accumulator4); 

      __m256 sumef = _mm256_hadd_ps(accumulator5, accumulator6); 
      __m256 sumgh = _mm256_hadd_ps(accumulator7, accumulator8); 

      __m256 sumabcd = _mm256_hadd_ps(sumab, sumcd); 
      __m256 sumefgh = _mm256_hadd_ps(sumef, sumgh); 

      __m128 sumabcd1 = _mm256_extractf128_ps(sumabcd, 0); 
      __m128 sumabcd2 = _mm256_extractf128_ps(sumabcd, 1); 
      __m128 sumefgh1 = _mm256_extractf128_ps(sumefgh, 0); 
      __m128 sumefgh2 = _mm256_extractf128_ps(sumefgh, 1); 

      sumabcd1 = _mm_add_ps(sumabcd1, sumabcd2); 
      sumefgh1 = _mm_add_ps(sumefgh1, sumefgh2); 

__m256 result =_mm256_insertf128_ps(_mm256_castps128_ps256(sumabcd1), sumefgh1, 1) 
+0

Ich denke nicht, dass Sie sich dabei stark verbessern können, aber wenn es wirklich performance-kritisch ist, beachten Sie, dass '_mm256_hadd_ps' typischerweise eine Latenz von 5 hat, während' _mm256_add_ps' eine Latenz von 3 hat Sie haben eine Wahl, auch wenn es ein paar Anweisungen hinzufügt. Intels IACA-Tool kann nützlich sein, um die relative Effizienz von kleinen Code-Fragmenten wie dieser zu vergleichen. –

+1

Sie brauchen also einen Vektor von horizontalen Summen von 8 Quellvektoren? Sie * könnten * zuerst transponieren und dann vertikale Summen, aber das ist wahrscheinlich viel langsamer. Schauen Sie sich [meine horizontale Summenantwort] (http://stackoverflow.com/questions/6996764/fastest-way-to-do-horizontal-float-vector-sum-on-x86/35270026#35270026) für Ideen an . Ein Teil der Arbeit mit anderen Insassen als Haddps ist wahrscheinlich gut. OTOH, du nimmst bei jedem Schritt den vollen Vorteil der Merging-Fähigkeit von 'hadd' und nimmst es niemals mit beiden Operanden gleich. 'extract (x, 0)' ist kostenlos, da es sich nur um eine Besetzung handelt. –

+1

Vielleicht können Sie Ihren Algorithmus so überdenken, dass Sie nur vertikalen Operator benötigen –

Antwort

3

können Sie 2x _mm256_permute2f128_ps verwenden, um die niedrigen und hohen Fahrspuren für eine vertikale vaddps aufreihen. Dies ist anstelle von 2x extractf128/insertf128. Dies macht auch zwei 128b vaddps xmm Anweisungen in eine einzige 256b vaddps ymm.

vperm2f128 ist so schnell wie eine einzige vextractf128 oder vinsertf128 auf Intel-CPUs. Bei AMD ist es allerdings langsam (8 m-Ops mit 4c Latenz bei der Bulldozer-Familie). Trotzdem, nicht so schlimm, dass Sie es vermeiden müssen, selbst wenn Sie sich für AMD interessieren. (Und eine der Permutationen kann tatsächlich eine vinsertf128 sein).


__m256 hsum8(__m256 a, __m256 b, __m256 c, __m256 d, 
      __m256 e, __m256 f, __m256 g, __m256 h) 
{ 
    // a = [ A7 A6 A5 A4 | A3 A2 A1 A0 ] 
    __m256 sumab = _mm256_hadd_ps(a, b); 
    __m256 sumcd = _mm256_hadd_ps(c, d); 

    __m256 sumef = _mm256_hadd_ps(e, f); 
    __m256 sumgh = _mm256_hadd_ps(g, h); 

    __m256 sumabcd = _mm256_hadd_ps(sumab, sumcd); // [ D7:4 ... A7:4 | D3:0 ... A3:0 ] 
    __m256 sumefgh = _mm256_hadd_ps(sumef, sumgh); // [ H7:4 ... E7:4 | H3:0 ... E3:0 ] 

    __m256 sum_hi = _mm256_permute2f128_ps(sumabcd, sumefgh, 0x31); // [ H7:4 ... E7:4 | D7:4 ... A7:4 ] 
    __m256 sum_lo = _mm256_permute2f128_ps(sumabcd, sumefgh, 0x20); // [ H3:0 ... E3:0 | D3:0 ... A3:0 ] 

    __m256 result = _mm256_add_ps(sum_hi, sum_lo); 
    return result; 
} 

Diese compiles as you'd expect. Der zweite permute2f128 kompiliert tatsächlich zu einem vinsertf128, da es nur die niedrige Spur jedes Eingangs in derselben Weise verwendet, die vinsertf128 tut. gcc 4.7 und später tun diese Optimierung, aber nur viel neuere clang-Versionen (v3.7). Wenn Sie sich um alten Klang kümmern, tun Sie dies auf der Quellenebene.

Die Einsparungen in den Quellzeilen sind größer als die Einsparungen in den Anweisungen, denn _mm256_extractf128_ps(sumabcd, 0); kompiliert zu Null-Anweisungen: es ist nur eine Besetzung. Kein Compiler sollte jemals vextractf128 mit einem imm8 ausgeben, außer 1. (vmovdqa xmm/m128, xmm ist immer besser für die untere Spur). Guter Job, Intel verschwendet ein Instruktions-Byte auf Zukunftssicherheit, das Sie nicht verwenden konnten, weil einfache VEX-Präfixe keinen Platz haben, um längere Vektoren zu kodieren.

Die beiden vaddps xmm Anweisungen können parallel ausgeführt werden, so dass die Verwendung einer einzelnen vaddps ymm meist nur eine Durchsatz (und Code-Größe) Verstärkung, nicht Latenz ist.

Wir rasieren 3 Zyklen von vollständig eliminiert die endgültige vinsertf128, obwohl.


vhaddps 3 UOPs, 5c Latenz und eine pro 2c Durchsatz. (6c Latenz auf Skylake). Zwei dieser drei Ups laufen auf dem Shuffle-Port. Ich denke, es macht im Grunde 2x shufps, Operanden für addps zu generieren.

Wenn wir haddps (oder zumindest einen horizontalen Betrieb, den wir verwenden können) mit einem einzigen shufps/addps oder so etwas emulieren können, würden wir voraus. Leider sehe ich nicht wie. Ein einzelner Shuffle kann nur ein Ergebnis mit Daten von zwei Vektoren erzeugen, aber wir benötigen beide Eingaben in die vertikale addps, um Daten von beiden Vektoren zu haben.

Ich glaube nicht, die horizontale Summe auf andere Weise zu tun, sieht vielversprechend aus. Normally, hadd is not a good choice, weil der gemeinsame Anwendungsfall der horizontalen Summe nur ein Element seiner Ausgabe interessiert.Das ist hier nicht der Fall: Jedes Element jedes hadd Ergebniss wird tatsächlich verwendet.

+0

Ah danke seit dem Start von AVX Ich bin immer noch verwirrt mit einigen der Permute und Shuffle-Operationen, da sie nicht ganz so einfach wie die alten SSE-Register sind. Jedes Mal, wenn ich 'Extrakt' verwende, weiß ich, dass es fast immer einen besseren Weg mit Shuffle gibt, aber ich konnte das nicht herausfinden. Sehr geschätzt. – user2927848

+0

@ user2927848: Es gibt Fälle, in denen einfügen/extrahieren nützlich sind. z.B. als erster Schritt in einer horizontalen Summe (oder einer anderen Reduktion). AVX ist hart, weil In-Lane-Sachen eine geringere Latenz haben als Cross-Lane-Zeug. (Und einige Sachen wie Cross-Lane-Permutionen ('vpermps') benötigen AVX2). –

Verwandte Themen