Es hört sich so an, als ob Sie einen Weg suchen, um zu bewerten, wie FPU-gebunden Ihr Code ist oder wie effektiv Sie die FPU verwenden, anstatt die Anzahl der Flops gemäß derselben anachronistischen Definition eines "Flops" zu zählen. Mit anderen Worten, Sie möchten eine Metrik, die den gleichen Spitzenwert erreicht, wenn jede Gleitkommaeinheit in jedem Zyklus mit voller Kapazität ausgeführt wird. Schauen wir uns eine Intel Sandy Bridge an, um zu sehen, wie sich dies auswirkt.
Hardware-unterstützte Gleitkommaoperationen
Dieser Chip unterstützt AVX- Befehle, sodass die Register 32 Byte lang sind (für 4 Doppel). Die superskalare Architektur ermöglicht eine Überlappung von Befehlen, wobei die meisten arithmetischen Befehle einige Zyklen in Anspruch nehmen, obwohl ein neuer Befehl möglicherweise mit dem nächsten Zyklus beginnen kann. Diese Semantik wird normalerweise durch Schreiben von Latenz / inversem Durchsatz abgekürzt. Ein Wert von 5/2 würde bedeuten, dass der Befehl 5 Zyklen dauert, Sie können jedoch jeden zweiten Zyklus einen neuen Befehl starten (vorausgesetzt, die Operanden sind verfügbar, also keine Daten) Abhängigkeit und nicht auf Erinnerung warten).
Es gibt drei Gleitkomma-Arithmetikeinheiten pro Kern, aber die dritte ist für unsere Diskussion nicht relevant. Wir bezeichnen die beiden relevanten Einheiten als A- und M-Einheiten, da ihre Hauptfunktionen Addition und Multiplikation sind. Beispielanweisungen (siehe Tabellen von Agner Fog )
vaddpd
: gepackte Addition, Belegung von Einheit A für 1 Zyklus, Latenz / Inverser Durchsatz beträgt 3/1
vmulpd
: gepackte Multiplikation, Einheit M, 5/1
vmaxpd
: gepackt wählen Sie paarweise maximal, Einheit A, 3/1
vdivpd
: gepackte Division, Einheit M (und etwas A), 21/20 bis 45/44, abhängig von der Eingabe
vsqrtpd
: gepackte Quadratwurzel, einige A und M, 21/21 bis 43/43 je nach Eingabe
vrsqrtps
: gepackte, niedriggenaue Kehrwurzel für die Eingabe mit einfacher Genauigkeit (8 floats
)
Die genaue Semantik für das, was sich überschneiden kann vdivpd
und vsqrtpd
anscheinend subtil und AFAIK ist, ist nirgendwo dokumentiert. In den meisten Fällen gibt es meines Erachtens kaum Überlappungsmöglichkeiten, obwohl der Wortlaut im Handbuch darauf hindeutet, dass mehrere Threads möglicherweise mehr Überlappungsmöglichkeiten in dieser Anweisung bieten. Wir können Peak Flops treffen, wenn wir in jedem Zyklus einen vaddpd
und starten vmulpd
, also insgesamt 8 Flops pro Zyklus. Dichte Matrix-Matrix-Multiplikation ( dgemm
) kann diesem Peak einigermaßen nahe kommen.
Wenn ich Flops für spezielle Anweisungen zähle, würde ich nachsehen, wie viel von der FPU belegt ist. Angenommen, Sie haben in Ihrem Eingabebereich vdivpd
durchschnittlich 24 Zyklen benötigt, um die Einheit M vollständig zu belegen, aber die Addition könnte (sofern verfügbar) gleichzeitig für die Hälfte der Zyklen ausgeführt werden. Die FPU ist in der Lage, während dieser Zyklen 24 gepackte Multiplikationen und 24 gepackte Additionen durchzuführen (perfekt verschachtelt vaddpd
und vmulpd
). Mit a vdivpd
können wir jedoch maximal 12 zusätzliche gepackte Additionen durchführen. Wenn wir annehmen, dass die bestmögliche Methode zum vdivpd
Teilen die Verwendung der Hardware (angemessen) ist, können wir die 36 gepackten "Flops" zählen, was darauf hinweist, dass wir jede skalare Teilung als 36 "Flops" zählen sollten.
Mit der reziproken Quadratwurzel ist es manchmal möglich, die Hardware zu übertreffen, insbesondere wenn nicht die volle Genauigkeit erforderlich ist oder wenn der Eingabebereich eng ist. Wie oben erwähnt, ist der vrsqrtps
Befehl sehr kostengünstig. Wenn Sie also eine Genauigkeit angeben, können Sie eine und vrsqrtps
anschließend ein oder zwei Newton-Iterationen ausführen, um zu bereinigen. Diese Newton-Iterationen sind gerecht
y *= (3 - x*y*y)*0.5;
Wenn viele dieser Operationen ausgeführt werden müssen, kann dies erheblich schneller sein als die naive Auswertung von y = 1/sqrt(x)
. Vor der Verfügbarkeit der ungefähren reziproken Quadratwurzel der Hardware verwendete ein leistungsabhängiger Code berüchtigte Ganzzahloperationen , um eine erste Vermutung für die Newton-Iteration zu finden.
Von der Bibliothek bereitgestellte mathematische Funktionen
Wir können eine ähnliche Heuristik auf von Bibliotheken bereitgestellte mathematische Funktionen anwenden. Sie können ein Profil erstellen, um die Anzahl der SSE-Anweisungen zu bestimmen, aber wie wir bereits besprochen haben, ist dies nicht die ganze Geschichte, und ein Programm, das seine ganze Zeit damit verbringt, spezielle Funktionen zu evaluieren, scheint möglicherweise nicht in die Nähe des Peaks zu gelangen, was zwar zutrifft, aber nicht zutrifft Es ist nicht hilfreich, Ihnen mitzuteilen, dass Sie die gesamte Zeit außerhalb Ihrer Kontrolle über die FPU verbringen.
Ich schlage vor, eine gute Vektor-Mathematik-Bibliothek als Basis zu verwenden (z. B. Intels VML, Teil von MKL). Messen Sie die Anzahl der Zyklen für jeden Aufruf und multiplizieren Sie diese Anzahl der Zyklen mit den maximal erreichbaren Flops. Wenn ein gepacktes Exponential also 50 Zyklen benötigt, um ausgewertet zu werden, zählen Sie es als 100 Flops mal die Registerbreite. Leider sind Vektor-Mathematik-Bibliotheken manchmal schwer aufzurufen und verfügen nicht über alle speziellen Funktionen. In diesem Fall würden Sie unsere hypothetische Skalarexponentialrechnung als 100 Flops zählen (obwohl es wahrscheinlich immer noch 50 dauert) Zyklen, so dass Sie nur 25% der "Spitze" erhalten, wenn die ganze Zeit damit verbracht wird, diese Exponentiale zu bewerten).
Wie bereits erwähnt, können Sie Zyklen und Hardware-Ereigniszähler über PAPI oder verschiedene Schnittstellen zählen. Zum einfachen Zählen von Zyklen können Sie den Zykluszähler direkt mithilfe der rdtsc
Anweisung mit einem Snippet der Inline-Assembly auslesen .