Verwendung von Fused Multiply-Add (FMA)-Anweisungen mit SSE/AVX

Lesezeit: 6 Minuten

Ich habe gelernt, dass einige Intel/AMD-CPUs simultan multiplizieren und mit SSE/AVX addieren können:
FLOPS pro Zyklus für Sandy-Bridge und Haswell SSE2/AVX/AVX2.

Ich möchte wissen, wie man das am besten im Code macht, und ich möchte auch wissen, wie es intern in der CPU gemacht wird. Ich meine mit der superskalaren Architektur. Nehmen wir an, ich möchte eine lange Summe wie die folgende in SSE ausführen:

//sum = a1*b1 + a2*b2 + a3*b3 +... where a is a scalar and b is a SIMD vector (e.g. from matrix multiplication)
sum = _mm_set1_ps(0.0f);
a1  = _mm_set1_ps(a[0]); 
b1  = _mm_load_ps(&b[0]);
sum = _mm_add_ps(sum, _mm_mul_ps(a1, b1));

a2  = _mm_set1_ps(a[1]); 
b2  = _mm_load_ps(&b[4]);
sum = _mm_add_ps(sum, _mm_mul_ps(a2, b2));

a3  = _mm_set1_ps(a[2]); 
b3  = _mm_load_ps(&b[8]);
sum = _mm_add_ps(sum, _mm_mul_ps(a3, b3));
...

Meine Frage ist, wie wird dies in gleichzeitiges Multiplizieren und Addieren umgewandelt? Können die Daten abhängig sein? Ich meine, kann die CPU tun _mm_add_ps(sum, _mm_mul_ps(a1, b1)) gleichzeitig oder müssen die bei der Multiplikation und Addition verwendeten Register unabhängig sein?

Und schließlich, wie gilt dies für die FMA (mit Haswell)? Ist _mm_add_ps(sum, _mm_mul_ps(a1, b1)) automatisch in eine einzelne FMA-Anweisung oder Mikrooperation umgewandelt?

Benutzeravatar von Mystical
Mystisch

Der Compiler darf eine getrennte Addition und Multiplikation fusionieren, obwohl dies das Endergebnis ändert (indem es genauer wird).

Ein FMA hat nur eine Rundung (es behält effektiv eine unendliche Genauigkeit für das interne temporäre Multiplikationsergebnis bei), während ein ADD + MUL zwei hat.

Die IEEE- und C-Standards erlauben dies, wenn #pragma STDC FP_CONTRACT ON ist in Kraft, und Compiler dürfen es haben ON standardmäßig (aber nicht alle). Gcc schließt standardmäßig Verträge mit der FMA ab (mit der Voreinstellung default -std=gnu*aber nicht -std=c*z.B -std=c++14). Für Klanges ist nur mit aktiviert -ffp-contract=fast. (Nur mit der #pragma aktiviert, nur innerhalb eines einzelnen Ausdrucks wie a+b*cnicht über separate C++-Anweisungen.).

Dies unterscheidet sich von strengem vs. entspanntem Gleitkomma (oder in gcc-Begriffen, -ffast-math vs. -fno-fast-math), die andere Arten von Optimierungen ermöglichen würde, die den Rundungsfehler abhängig von den Eingabewerten erhöhen könnten. Das Besondere an diesem ist die unendliche Präzision des FMA-internen Provisoriums; Wenn es im internen Temporär überhaupt eine Rundung gäbe, wäre dies im strikten FP nicht zulässig.

Selbst wenn Sie entspanntes Fließkomma aktivieren, entscheidet sich der Compiler möglicherweise trotzdem dafür, nicht zu fusionieren, da er möglicherweise erwartet, dass Sie wissen, was Sie tun, wenn Sie bereits systeminterne verwenden.


So der beste Weg Um sicherzustellen, dass Sie tatsächlich die gewünschten FMA-Anweisungen erhalten, verwenden Sie tatsächlich die bereitgestellten Intrinsics für sie:

FMA3-Intrinsik: (AVX2 – Intel-Haswell)

  • _mm_fmadd_pd()_mm256_fmadd_pd()
  • _mm_fmadd_ps(), _mm256_fmadd_ps()
  • und ungefähr eine Unmenge anderer Variationen …

FMA4-Intrinsik: (XOP – AMD Bulldozer)

  • _mm_macc_pd(), _mm256_macc_pd()
  • _mm_macc_ps(), _mm256_macc_ps()
  • und ungefähr eine Unmenge anderer Variationen …

  • Danke, das beantwortet meine Frage zur FMA mehr oder weniger. Ich sollte wirklich etwas Zeit damit verbringen, etwas x86-Assembler zu lernen. Das würde wahrscheinlich die meisten meiner Fragen beantworten.

    Benutzer2088790

    10. April 2013 um 18:39 Uhr

  • Zu Ihrer Frage, ob eine Multiplikation und eine Addition gleichzeitig durchgeführt werden können (FMA). Die Antwort ist nein, da die Addition das Ergebnis der Multiplikation verwendet. Sie essen also die Latenz von Addieren + Multiplizieren. Eine FMA-Instruktion führt beide Instruktionen zusammen aus – normalerweise mit der gleichen Latenz wie eine einzelne Multiplikation. Das Hinzufügen ist also kostenlos.

    – Mystisch

    10. April 2013 um 18:45 Uhr


  • Danke, das dachte ich mir. Jetzt muss ich nur noch herausfinden, wie ich meinen Code so organisiere, dass die Summe, wie ich sie oben definiert habe, gleichzeitig unabhängig addiert und multipliziert (damit ich Latenzen vermeide).

    Benutzer2088790

    10. April 2013 um 19:10 Uhr

  • Sie müssen sie nur so weit voneinander trennen, bis der maximale Durchsatz erreicht ist. Der kritische Pfad befindet sich auf den Zugängen. Die Latenz einer addps sind 3 Zyklen. Der Durchsatz beträgt jedoch 1. Sie benötigen also mindestens 3 separate Summenketten, um ihn vollständig zu nutzen. Sie haben derzeit 4, also ist das ausreichend.

    – Mystisch

    10. April 2013 um 19:41 Uhr


  • Ich denke, Ihre Antwort ist irreführend, da ein Compiler standardmäßig FMA verwenden kann, ohne gegen die IEEE-Regeln zu verstoßen. stackoverflow.com/a/34817983/2542702

    – Z-Boson

    19. Januar 2016 um 12:55 Uhr

Benutzeravatar von Z boson
Z-Boson

Ich habe den folgenden Code in GCC 5.3, Clang 3.7, ICC 13.0.1 und MSVC 2015 (Compiler-Version 19.00) getestet.

float mul_add(float a, float b, float c) {
    return a*b + c;
}

__m256 mul_addv(__m256 a, __m256 b, __m256 c) {
    return _mm256_add_ps(_mm256_mul_ps(a, b), c);
}

Mit den richtigen Compiler-Optionen (siehe unten) generiert jeder Compiler eine vfmadd Anleitung (zB vfmadd213ss) aus mul_add. Allerdings kann nur MSVC keinen Vertrag abschließen mul_addv zu einem einzigen vfmadd Anleitung (zB vfmadd213ps).

Die folgenden Compileroptionen reichen zum Generieren aus vfmadd Anleitung (außer mit mul_addv mit MSVC).

GCC:   -O2 -mavx2 -mfma
Clang: -O1 -mavx2 -mfma -ffp-contract=fast
ICC:   -O1 -march=core-avx2
MSVC:  /O1 /arch:AVX2 /fp:fast

GCC 4.9 zieht sich nicht zusammen mul_addv zu einer einzelnen fma-Anweisung, aber seit mindestens GCC 5.1 tut es. Ich weiß nicht, wann die anderen Compiler damit begonnen haben.

  • Siehe auch #pragma STDC FP_CONTRACT ON. Stephen Canon weist darauf hin, dass es eine Kontraktion nur innerhalb einer einzelnen Anweisung zulässt, nicht über Anweisungen hinweg. (listen.llvm.org/pipermail/cfe-dev/2015-September/045110.html). Beachten Sie auch, dass gcc die Kontraktion nur mit ermöglicht -std=gnu*nicht mit -std=c11 oder Wasauchimmer. (Und dann ermöglicht es eine Kontraktion über Anweisungen hinweg, die über das hinausgeht, was IEEE + ISO C strikt zulassen). Eine andere Testfunktion, die separate Variablen verwendet, könnte einen Versuch wert sein.

    – Peter Cordes

    8. September 2017 um 19:37 Uhr

  • @PeterCordes, siehe diese stackoverflow.com/q/34436233/2542702 und die Antwort von Stephen Canon. Ich denke, was GCC tut, ist laut Stephens Antwort in Ordnung (vorausgesetzt, GCC hat es nicht ignoriert STDC FP_CONTRACT was beim letzten Mal, als ich es überprüft habe, leider nicht der Fall ist).

    – Z-Boson

    11. September 2017 um 11:17 Uhr

  • Ihre Frage dort fragt nur ungefähr return a*b + c;nicht über float mul = a*b; return mul + c;. Lesen Sie Stephens Post auf der Mailingliste sorgfältig durch: Er erwähnt dieses Geräusch STDC FP_CONTRACT ON ermöglicht im Gegensatz zu Clangs nur die Kontraktion innerhalb eines Ausdrucks -ffp-contract=fast was es auch für mein zweites Beispiel in diesem Kommentar ermöglichen würde. Deshalb hat clang getrennt on vs. fast Einstellungen für die Befehlszeilenoption. Siehe meine letzten Änderungen an der Antwort von Mystcial zu dieser Frage. Es ist chaotischer als ich zuerst dachte 🙁

    – Peter Cordes

    11. September 2017 um 17:19 Uhr


  • @PeterCordes, einer meiner Punkte ist, dass GCC ignoriert #pragma STDC FP_CONTRACT. Zumindest habe ich das letzte Mal nachgesehen. Ich sollte das noch einmal überprüfen (für zB gnuc99 und c99 oder was auch immer).

    – Z-Boson

    13. September 2017 um 10:51 Uhr

  • Ich denke, das stimmt immer noch. Und sein tatsächliches Verhalten geht darüber hinaus #pragma STDC FP_CONTRACT ON erlaubt, also ist es nicht ganz so, als würde man das standardmäßig auf ON setzen und keine Möglichkeit bieten, es auszuschalten. Ich denke nach dem, was ich gelesen habe, dass IEEE + C a nicht spezifiziert #pragma STDC FP_CONTRACT FASTobwohl das ein nützlich Einstellung.

    – Peter Cordes

    13. September 2017 um 15:28 Uhr

1407910cookie-checkVerwendung von Fused Multiply-Add (FMA)-Anweisungen mit SSE/AVX

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy