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?
quelle
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.Antworten:
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/1vmulpd
: gepackte Multiplikation, Einheit M, 5/1vmaxpd
: gepackt wählen Sie paarweise maximal, Einheit A, 3/1vdivpd
: gepackte Division, Einheit M (und etwas A), 21/20 bis 45/44, abhängig von der Eingabevsqrtpd
: gepackte Quadratwurzel, einige A und M, 21/21 bis 43/43 je nach Eingabevrsqrtps
: gepackte, niedriggenaue Kehrwurzel für die Eingabe mit einfacher Genauigkeit (8floats
)Die genaue Semantik für das, was sich überschneiden kann
vdivpd
undvsqrtpd
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 einenvaddpd
und startenvmulpd
, 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 verschachteltvaddpd
undvmulpd
). Mit avdivpd
können wir jedoch maximal 12 zusätzliche gepackte Additionen durchführen. Wenn wir annehmen, dass die bestmögliche Methode zumvdivpd
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 undvrsqrtps
anschließend ein oder zwei Newton-Iterationen ausführen, um zu bereinigen. Diese Newton-Iterationen sind gerechtWenn 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 .quelle
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.
quelle
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:
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.
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.
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.
quelle
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.
quelle