FLOP-Zählung für Bibliotheksfunktionen

13

Wenn man die Anzahl der FLOPs in einer einfachen Funktion auswertet, kann man oft einfach den Ausdruck mit den Grundrechenarten durchgehen. Bei mathematischen Aussagen mit gerader Division kann man dies jedoch nicht tun und erwartet, mit FLOP-Zählungen von Funktionen, die nur Additionen und Multiplikationen enthalten, vergleichen zu können. Die Situation ist noch schlimmer, wenn die Operation in einer Bibliothek implementiert ist. Daher ist es unbedingt erforderlich, eine angemessene Vorstellung von der Leistung der Sonderfunktionen zu haben.

Mit Sonderfunktionen meinen wir Dinge wie:

  • exp ()
  • sqrt ()
  • sin / cos / tan ()

die in der Regel von Systembibliotheken bereitgestellt werden.

Das Bestimmen der Komplexität von diesen wird noch weiter durch die Tatsache verwechselt, dass viele von ihnen adaptiv sind und eingabeabhängige Komplexität aufweisen. Beispielsweise skalieren numerisch stabile Implementierungen von exp () häufig adaptiv neu und verwenden Look-ups. Mein erster Eindruck hier ist, dass das Beste, was man in diesem Fall tun kann, das durchschnittliche Verhalten der Funktionen ist.

Diese gesamte Diskussion hängt natürlich stark von der Architektur ab. Für diese Diskussion können wir uns auf traditionelle Allzweckarchitekturen beschränken und solche mit speziellen Funktionseinheiten (GPUs usw.) ausschließen.

Man kann relativ einfache Versuche finden, diese für bestimmte Architekturen zu standardisieren, um einen Vergleich zwischen System und System zu erzielen. Dies ist jedoch nicht akzeptabel, wenn man sich um die Leistung von Methode und Methode kümmert. Welche Methoden zur Bestimmung der FLOP-Komplexität dieser Funktionen werden als akzeptabel angesehen? Gibt es größere Fallstricke?

Peter Brune
quelle
Peter, nur ein kurzer Kommentar. Obwohl Sie einige gute Beispiele für Funktionen bereitstellen, die von mathematischen Bibliotheken bereitgestellt werden, werden Gleitkommadivisionen normalerweise von der Gleitkommaeinheit implementiert.
Aron Ahmadia
Vielen Dank! Ich war nicht klar genug. Ich habe gerade bearbeitet, um einen besseren Kontrast zu erzielen.
Peter Brune
Ich war überrascht, dass sin, cos und sqrt auch in der x87-Gleitkomma-Untermenge von x86-Befehlen implementiert sind. Ich glaube, ich verstehe Ihren Standpunkt, aber ich denke, die akzeptierte Praxis besteht darin, diese als Gleitkommaoperationen mit etwas größeren Konstanten zu behandeln :)
Aron Ahmadia
@AronAhmadia Seit über einem Jahrzehnt gibt es keinen Grund, x87 zu verwenden. Teilen und sqrt()sind in SSE / AVX, aber sie dauern viel länger als Addition und Multilikation. Außerdem sind sie in Sandy Bridge AVX schlecht vektorisiert, was doppelt so lange dauert wie der SSE-Befehl (mit der halben Breite). Zum Beispiel kann AVX mit doppelter Genauigkeit (4 Doppelte Breite) eine gepackte Multiplikation und gepackte Addition für jeden Zyklus durchführen (vorausgesetzt, es gibt keine Abhängigkeiten oder Verzögerungen im Speicher), was 8 Flops pro Zyklus entspricht. Die Division dauert zwischen 20 und 44 Zyklen, um diese "4 Flops" durchzuführen.
Jed Brown
sqrt () ist auf PowerPC optional. Viele Embedded-Chips dieser Architektur implementieren den Befehl nicht, z. B. die Freescale MPC5xxx-Serie.
Damien

Antworten:

10

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 vdivpdund vsqrtpdanscheinend 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 vaddpdund 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 vdivpddurchschnittlich 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 vaddpdund vmulpd). Mit a vdivpdkönnen wir jedoch maximal 12 zusätzliche gepackte Additionen durchführen. Wenn wir annehmen, dass die bestmögliche Methode zum vdivpdTeilen 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 vrsqrtpsBefehl sehr kostengünstig. Wenn Sie also eine Genauigkeit angeben, können Sie eine und vrsqrtpsanschließ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 rdtscAnweisung mit einem Snippet der Inline-Assembly auslesen .

Jed Brown
quelle
7

Mit PAPI , das den Zugriff auf Hardwarezähler ermöglicht, und einfachen Testprogrammen können Sie sie auf realen Systemen zählen . Mein Lieblings-PAPI-Interface / Wrapper ist IPM (Integrated Performance Monitor), es gibt jedoch auch andere Lösungen ( z. B. TAU ). Dies sollte einen ziemlich stabilen Methodenvergleich ergeben.

Max Hutchinson
quelle
4

Ich werde diese Frage so beantworten, als ob Sie gefragt hätten:

"Wie vergleiche oder prognostiziere ich die Leistung von Algorithmen, die stark von speziellen Funktionen abhängen, anstatt der traditionellen Multiplikations-Additions-Übertrags-FLOP-Zählungen, die aus der numerischen linearen Algebra stammen?"

Ich stimme Ihrer ersten Annahme zu, dass die Leistung vieler spezieller Funktionen von der Architektur abhängt und dass, obwohl Sie normalerweise jede dieser Funktionen als konstant kostenpflichtig behandeln können, die Größe der Konstanten auch zwischen zwei Prozessoren desselben Typs variiert Firma, aber mit unterschiedlichen Architekturen (siehe Agner Fog's Anweisungszeitplan als Referenz).

Ich bin jedoch anderer Meinung, dass der Schwerpunkt des Vergleichs auf den Kosten der einzelnen Gleitkommaoperationen liegen sollte. Ich denke, dass das Zählen von FLOPs bis zu einem gewissen Grad immer noch nützlich ist, aber dass es einige viel wichtigere Überlegungen gibt, die die Kosten spezieller Funktionen beim Vergleich zweier potenzieller Algorithmen weniger relevant machen können, und diese sollten zuerst explizit untersucht werden, bevor auf einen Vergleich von zugegriffen wird Gleitkommaoperationen:

  1. Skalierbarkeit - Algorithmen mit Aufgaben, die effizient auf parallelen Architekturen implementiert werden können, werden auf absehbare Zeit das Gebiet des wissenschaftlichen Rechnens dominieren. Ein Algorithmus mit einer besseren "Skalierbarkeit", sei es durch eine geringere Kommunikation, einen geringeren Synchronisationsbedarf oder einen besseren natürlichen Lastausgleich, verwendet möglicherweise langsamere Sonderfunktionen und ist daher für eine geringe Anzahl von Prozessen langsamer, holt jedoch letztendlich die Anzahl auf der Prozessoren wird erhöht.

  2. Temporale Referenzlokalität - Verwendet der Algorithmus Daten zwischen Tasks erneut, sodass der Prozessor unnötigen Speicherverkehr vermeiden kann? Jede Ebene der Speicherhierarchie, die ein Algorithmus durchläuft, fügt jedem Speicherzugriff (ungefähr) eine weitere Größenordnung hinzu. Infolgedessen ist ein Algorithmus mit einer hohen Dichte von Spezialoperationen wahrscheinlich wesentlich schneller als ein Algorithmus mit der entsprechenden Anzahl von einfachen Funktionsoperationen über einen größeren Speicherbereich.

  3. Speicherbedarf - Dies hängt stark mit den vorherigen Punkten zusammen, aber wenn Computer immer größer werden, geht die Speicherkapazität pro Kern tatsächlich nach unten. Ein kleiner Speicherbedarf hat zwei Vorteile. Das erste ist, dass eine kleine Menge von Programmdaten wahrscheinlich vollständig in den Prozessor-Cache passen wird. Zum anderen kann bei sehr großen Problemen ein Algorithmus mit geringerem Speicherbedarf in den Prozessorspeicher passen, wodurch Probleme gelöst werden können, die ansonsten die Leistungsfähigkeit des Computers übersteigen würden.

Aron Ahmadia
quelle
Ich würde behaupten, dass die Kenntnis von FLOPS / Sek. Es Ihnen ermöglicht, zu unterscheiden, in welchem ​​Engpassregime (Speicher, Kommunikation) Sie sich ziemlich gut befinden. Betrachten Sie zum Beispiel Newton-Krylov-Methoden, die einen Großteil ihrer Zeit mit Matvecs verbringen. Matvecs machen ein oder zwei FLOPs pro Matrixeintrag und das wars. Nicht zusammengebaute Glätter haben das Potenzial, es besser zu machen. Jed und ich haben auch darüber gesprochen, und eine andere Idee ist, zu sehen, wie viele Zyklen Sie für FLOP-gebundene Berechnungen verwenden. Dies kann jedoch eine sehr genaue Überwachung erfordern, und Gesamt-FLOPS / Sek. Sind möglicherweise praktischer.
Peter Brune
Aron, die meiste Antwort scheint Peters Frage zu umgehen, um diese andere Frage zu beantworten: scicomp.stackexchange.com/questions/114
Jed Brown
@JedBrown, ich stimme zu, danke, dass Sie sich die Zeit genommen haben, eine viel solidere Antwort zusammenzustellen.
Aron Ahmadia
0

Warum sich die Mühe machen, Flops zu zählen? Zählen Sie einfach die Zyklen für jede Operation und Sie haben etwas, das universell ist.

Jeff
quelle