Die Intel Optimization Reference in Abschnitt 3.5.1 empfiehlt:
"Anweisungen für Einzel-Mikro-Operationen bevorzugen."
"Vermeiden Sie die Verwendung komplexer Anweisungen (z. B." Enter "," Leave "oder" Loop "), die mehr als 4 Mikrobefehle enthalten und mehrere Zyklen zum Decodieren erfordern. Verwenden Sie stattdessen Sequenzen einfacher Anweisungen."
Obwohl Intel selbst Compiler-Schreibern befiehlt, Anweisungen zu verwenden, die zu wenigen Mikro-Ops dekodieren, kann ich in keinem ihrer Handbücher etwas finden, das erklärt, wie viele Mikro-Ops jeder ASM-Befehl dekodiert! Sind diese Informationen überall verfügbar? (Natürlich erwarte ich, dass die Antworten für verschiedene Generationen von CPUs unterschiedlich sein werden.)
Agner Fogs PDF Dokument auf x86-Anweisungen (verlinkt von der Hauptseite, die Hans zitiert) ist die einzige Referenz Ich habe auf Anweisung Timings und Mikro-Ops gefunden. Ich habe noch nie ein Intel-Dokument zum Mikro-Ausfall gesehen.
Es wurde bereits darauf hingewiesen, dass Agner Fogs Optimierungshandbücher eine ausgezeichnete Ressource sind, insbesondere seine Instruction Tables , die fast vollständig für alle interessierenden x86-Mikroarchitekturen sind.
Aber Sie haben noch eine andere Möglichkeit: Intels Architektur-Code-Analysator (IACA) . Es gibt eine Beschreibung darüber, wie man es hier auf Stack verwenden kann Überlauf , aber es ist ziemlich einfach (obwohl es für eine einmalige Analyse ein wenig mühsam ist). Sie laden einfach die ausführbare Datei herunter, geben einen Prolog- und Epilog-Code um den zu analysierenden Anweisungsblock herum (es enthält eine C-Kopfzeile für diesen Zweck ( iacaMarks.h
), die mit verschiedenen Compilern funktioniert, oder Sie können Ihren Assembler dazu anweisen strende die entsprechenden Bytes) und führe dann deine Binärdatei durch iaca.exe
. Die aktuelle Version (v2.2) unterstützt nur 64-Bit-Binärdateien, aber das ist keine große Einschränkung, da die Analyse auf Befehlsebene für 32-Bit- und 64-Bit-Modi nicht wesentlich anders ist. Die aktuelle Version unterstützt auch alle modernen Intel-Mikroarchitekturen, die für einen professionellen Softwareentwickler von Nehalem bis Broadwell interessant sein könnten.
Die Ausgabe, die Sie von diesem Tool erhalten, zeigt Ihnen an, auf welchen Ports ein bestimmter Befehl ausgeführt werden kann und wie viele μops dieser Befehl in der angegebenen Mikroarchitektur zerlegt wird.
Das ist so nah wie möglich, um eine direkte Antwort auf Ihre Frage zu bekommen, denn als Hans Passant wies in den Kommentaren darauf hin, dass die genauen μops, die jeder Befehl abbaut, absichtlich sind geheim gehalten von Intel. Sie sind nicht nur ein proprietäres Geschäftsgeheimnis, sondern Intel will frei sein, wie es von einer Mikroarchitektur zur anderen funktioniert. Tatsächlich ist , wie viele μops eine Anweisung zerlegt, ist jedoch alles, was Sie jemals wissen würden, wenn Sie Code optimieren. Es ist egal, was die Anweisung zerlegt.
Aber ich wiederhole einen Teil von Peter Cordes 'Antwort : "In manchen Fällen ist es leicht zu erraten, obwohl ". Wenn Sie diese Art von detaillierten Informationen für jede Anweisung, die Sie in Betracht ziehen, nachschlagen müssen, werden Sie eine Menge Zeit verschwenden. Sie werden sich auch wahnsinnig machen, denn wie Sie bereits wissen, variiert es von Mikroarchitektur zu Mikroarchitektur. Der eigentliche Trick besteht darin, ein intuitives Gefühl dafür zu bekommen, welche Anweisungen in der x86-ISA "einfach" und welche "komplex" sind. Es sollte ziemlich offensichtlich sein, wenn Sie die Dokumentation lesen, und dieses intuitive Gefühl ist wirklich alles, was die Optimierungsempfehlungen von Intel Ihnen aufzeigen. Vermeiden Sie "komplexe" (alte CISC-artige) Anweisungen wie LOOP
, ENTER
, LEAVE
und so weiter. ZB bevorzugen Sie DEC
+ JNZ
über LOOP
. Relativ gesehen gibt es nur eine kleine Minderheit von "klassischen" x86-Anweisungen, die zu mehr als ein oder zwei μops dekodieren. * Wenn Sie die Ausgabe eines guten optimierenden Compilers studieren, werden Sie auch in die richtige Richtung führen, da Sie nie sehen werden, dass Compiler diese "komplexen" Anweisungen verwenden.
Etwas gegen Peters Antwort, aber ich bin mir ziemlich sicher, dass der zitierte Abschnitt der Optimierungshandbücher von Intel nicht ist, der sich auf die SIMD-Anweisungen bezieht. Sie sprechen über die CISC-Anweisungen der alten Schule, die in Mikrocode implementiert sind und die sie bereits gelöscht hätten, wenn sie sie nicht aus Gründen der Abwärtskompatibilität unterstützen müssten. Wenn Sie das Verhalten von SSE3 HADDPS
benötigen, dann ist es wahrscheinlich besser, HADDPS
zu verwenden, anstatt es in "einfachere" Komponenten zu zerlegen. (Es sei denn natürlich, Sie können diese Operationen besser planen, indem Sie sie mit nicht verwandtem Code verschachteln. Aber das ist in der Praxis sehr schwierig.)
* Um vollkommen genau zu sein, gibt es bestimmte scheinbar einfache Anweisungen, die tatsächlich unter Verwendung von Mikrocode implementiert werden und zu mehreren μops zerlegen. Eine 64-Bit-Division ( DIV
) ist ein Beispiel. Wenn ich mich richtig erinnere, ist dies mikrocodiert mit etwa 30-40 μops (variabel). Dies ist jedoch keine Anweisung, die Sie vermeiden sollten. Dies zeigt, dass die Handbücher von Intel hier sehr allgemein gehalten sind. Wenn Sie eine Division erstellen müssen, verwenden Sie DIV
. Natürlich bevorzugen nicht Divisionen bei der Optimierung für die Geschwindigkeit, aber versuchen Sie nicht, Ihren eigenen Divisionalgorithmus zu schreiben, nur um die mikrocodierte DIV
zu vermeiden!
Die andere große Ausnahme ist die Zeichenkette Anweisungen . Der Performance-Kalkül für diese ist etwas komplizierter als "Vermeiden Sie, weil sie zu mehreren μops dekodieren".
Glücklicherweise ist eine Sache einfach: Verwenden Sie niemals die String-Anweisungen ohne ein REP
Präfix. Das macht einfach keinen Sinn, und Sie erhalten eine deutlich bessere Leistung, wenn Sie die Anweisung selbst in die einfacheren Komponentenanweisungen zerlegen - zum Beispiel MOVSB
→ MOV AL, [ESI]
+ MOV ES:[EDI], AL
+ INC/DEC ESI
+ INC/DEC EDI
.
Wo es etwas schwieriger zu entscheiden ist, wenn Sie beginnen, das Präfix REP
zu nutzen. Obwohl dies dazu führt, dass die Anweisung in viele μops decodiert wird, ist es manchmal noch effizienter, die wiederholten String-Anweisungen zu verwenden, als die Schleife selbst manuell zu codieren. Aber nicht immer. Es gab viele Diskussionen zu diesem Thema bereits auf Stack Overflow und anderswo; Siehe zum Beispiel diese Frage .
Eine detaillierte Analyse ist wirklich außerhalb des Rahmens dieser Antwort, aber meine schnelle Faustregel ist, dass Sie REP LOADS
, REP SCAS
und REP CMPS
ganz vergessen können. Auf der anderen Seite sind REP MOVS
und REP STOS
nützlich, wenn Sie eine einigermaßen große Anzahl von Wiederholungen benötigen. Anzahl der Male . Verwenden Sie immer die größtmögliche Wortgröße: DWORD auf 32-Bit, QWORD auf 64-Bit (aber beachten Sie, dass Sie auf modernen Prozessoren sind besser dran mit MOVSB
/ STOSB
, da sie intern größere Mengen verschieben können . Und selbst wenn all diese Bedingungen erfüllt sind, wenn Ihr Ziel Vektoranweisungen zur Verfügung hat, möchten Sie das wahrscheinlich überprüfen es wäre nicht schneller, die Verschiebung / Speicherung mit Vektorbewegungen zu implementieren.
Siehe auch Allgemeine Hinweise von Agner Fog auf Seite 150 .
Agner Fogs Tabellen zeigen, auf welchen Port-Mikro-Ops alles läuft, was für die Performance wichtig ist. Es zeigt nicht genau, was jeder UOP macht, denn das kann man nicht rückentwickeln. (d. h. welche Ausführungseinheit an diesem Port verwendet wird).
In einigen Fällen ist es jedoch leicht zu erraten: haddps
auf Haswell ist 1 up für Port und 2 ups für Port 5. Das sind ziemlich offensichtlich 2 Shuffle (Port 5) und ein FP-Add (Port 1). Es gibt viele andere Ausführungseinheiten an Port 5, z. Vektor boolean, SIMD Integer add, und viele skalare Integer-Sachen, aber da haddps
überhaupt mehrere Ups benötigt, ist es ziemlich offensichtlich, dass Intel es mit Shuffles und einem normalen "vertikalen" Add-Up implementiert.
Es könnte möglich sein, etwas über die Abhängigkeitsbeziehung zwischen diesen uops herauszufinden (z. B. 2 Shuffs-Style-Shuffle, die eine FP-Zugabe speisen, oder ist es shuffle-add-shuffle?). Wir sind uns auch nicht sicher, ob die Shuffles voneinander unabhängig sind oder nicht: Haswell hat nur einen Shuffle-Port, so dass der Ressourcenkonflikt eine Gesamtlatenz von 5c ergibt, da die Shuffles nicht parallel laufen könnten, selbst wenn sie unabhängig wären / p>
Beide Shuffle-Ups benötigen wahrscheinlich beide Eingänge, so dass selbst wenn sie unabhängig voneinander sind, ein Eingang früher als der andere bereit ist, nicht die Latenz für den kritischen Pfad verbessert (vom langsameren Eingang zum Ausgang) .
Wenn es möglich wäre, HADDPS mit zwei unabhängigen Shuffle mit einem Eingang zu implementieren, würde das bedeuten, dass HADDPS xmm0, xmm1 in einer Schleife, in der xmm1 eine Konstante ist, der dep-Kette von xmm0 nur 4c Latenz hinzufügen würde. Ich habe es nicht gemessen, aber ich denke, es ist unwahrscheinlich; Es ist fast sicher, dass es zwei unabhängige 2-Input-Shuffle zum Zuführen eines ADDPS-Ups ist.
Tags und Links x86 compiler-optimization intel