Die Optimierungsleitfäden von Agner Fog sind ausgezeichnet. Er verfügt über Handbücher, Tabellen mit Befehlszeiten und Dokumente zur Mikroarchitektur aller neueren x86-CPU-Designs (bis hin zu Intel Pentium). Siehe auch einige andere Ressourcen, die von /programming//tags/x86/info verlinkt sind
Nur zum Spaß beantworte ich einige der Fragen (Zahlen von aktuellen Intel-CPUs). Die Wahl der Ops ist nicht der Hauptfaktor für die Optimierung des Codes (es sei denn, Sie können eine Aufteilung vermeiden.)
Ist eine einzelne Multiplikation auf der CPU langsamer als eine Addition?
Ja (es sei denn, es ist durch eine Potenz von 2). (3-4-fache Latenz mit nur einem Durchsatz pro Takt bei Intel.) Gehen Sie jedoch nicht zu weit, um dies zu vermeiden, da es nur 2 oder 3 Mal schneller ist.
Was genau sind die Geschwindigkeitsmerkmale der grundlegenden mathematischen und Kontrollfluss-Opcodes?
Siehe Agner Fog Instruktionstabellen und Mikroarchitektur Anleitung , wenn Sie wissen wollen , genau : P. Sei vorsichtig mit bedingten Sprüngen. Bedingungslose Sprünge (wie Funktionsaufrufe) haben einen geringen Overhead, aber nicht viel.
Wenn zwei Opcodes die gleiche Anzahl von Zyklen benötigen, um ausgeführt zu werden, können beide ohne Leistungsgewinn / -verlust austauschbar verwendet werden.
Nein, sie konkurrieren möglicherweise um den gleichen Ausführungsport wie etwas anderes, oder sie konkurrieren möglicherweise nicht. Dies hängt davon ab, an welchen anderen Abhängigkeitsketten die CPU parallel arbeiten kann. (In der Praxis ist in der Regel keine sinnvolle Entscheidung zu treffen. Gelegentlich kann es vorkommen, dass Sie eine Vektorverschiebung oder eine Vektorverschiebung verwenden, die auf verschiedenen Ports von Intel-CPUs ausgeführt werden. Das gesamte Register wird jedoch byteweise verschoben.) PSLLDQ
etc.) läuft in der Shuffle Unit.)
Alle anderen technischen Details, die Sie zur x86-CPU-Leistung mitteilen können, sind willkommen
In den Microarch-Dokumenten von Agner Fog werden die Pipelines von Intel- und AMD-CPUs detailliert genug beschrieben, um genau zu bestimmen, wie viele Zyklen eine Schleife pro Iteration dauern soll und ob es sich um einen UOP-Durchsatz, eine Abhängigkeitskette oder einen Konflikt um einen Ausführungsport handelt. Sehen Sie sich einige meiner Antworten auf StackOverflow an, wie diese oder diese .
Auch http://www.realworldtech.com/haswell-cpu/ (und ähnliches für frühere Designs) macht das Lesen Spaß, wenn Sie CPU-Design mögen.
Hier ist Ihre Liste, sortiert nach einer Haswell-CPU, basierend auf meinen besten Gästezahlen. Dies ist jedoch keine wirklich nützliche Methode, um über Dinge nachzudenken, außer eine ASM-Schleife abzustimmen. Cache- / Verzweigungsvorhersageeffekte dominieren normalerweise. Schreiben Sie Ihren Code, um gute Muster zu erhalten. Zahlen sind sehr wellenförmig und versuchen, eine hohe Latenz zu berücksichtigen, auch wenn der Durchsatz kein Problem darstellt, oder mehr Uops zu generieren, die die Pipe verstopfen, damit andere Dinge parallel ablaufen. Esp. Die Cache / Branch-Nummern sind sehr zusammengesetzt. Latenz ist wichtig für schleifenbasierte Abhängigkeiten, Durchsatz ist wichtig, wenn jede Iteration unabhängig ist.
TL: DR Diese Zahlen basieren auf dem, was ich mir für einen "typischen" Anwendungsfall vorstelle, was die Kompromisse zwischen Latenz, Ausführungsport-Engpässen und Front-End-Durchsatz (oder Verzögerungen bei Zweigniederlassungen) betrifft ). Bitte verwenden Sie diese Zahlen nicht für ernsthafte Perfektionsanalysen .
- 0,5 bis 1 Bitweise / Ganzzahlige Addition / Subtraktion /
Verschieben und Drehen ( konstante Anzahl zur Kompilierungszeit) /
Vektorversionen von all diesen (1 bis 4 pro Zyklusdurchsatz, 1 Zykluslatenz )
- 1 Vektor min, max, vergleiche-gleich, vergleiche-größer (um eine Maske zu erstellen)
- 1,5 Vektormischungen. Haswell und neuere Versionen haben nur einen Shuffle-Port, und meiner Meinung nach ist es üblich, viel zu mischen, wenn Sie etwas benötigen. Deshalb gewichte ich es etwas höher, um zum Nachdenken über die Verwendung von weniger Mischen anzuregen. Sie sind nicht frei, esp. Wenn Sie eine pshufb-Kontrollmaske aus dem Speicher benötigen.
- 1,5 Laden / Speichern (L1-Cache-Treffer. Durchsatz besser als Latenz)
- 1,75 Integer Multiplication (3 c Latenz / 1 c Tput bei Intel, 4 c Lat bei AMD und nur 1 c Tput bei Intel). Kleine Konstanten sind mit LEA und / oder ADD / SUB / shift noch günstiger . Aber natürlich sind Konstanten zur Kompilierungszeit immer gut und können oft in andere Dinge optimiert werden. (Und Multiplikation in einer Schleife kann oft vom Compiler auf
tmp += 7
eine Schleife reduziert werden anstatt tmp = i*7
)
- 1,75 einige 256b-Vektor-Shuffle (zusätzliche Latenz auf Insns, die Daten zwischen 128b-Spuren eines AVX-Vektors verschieben können). (Oder 3 bis 7 auf Ryzen, wo Spurwechsel viel mehr Uops benötigen)
- 2 fp add / sub (und Vektorversionen davon) (1 oder 2 pro Zyklusdurchsatz, 3 bis 5 Zykluslatenz). Kann langsam sein, wenn Sie einen Engpass bei der Latenz haben, z. B. wenn Sie ein Array mit nur einer
sum
Variablen summieren . (Ich könnte dies und fp mul so niedrig wie 1 oder so hoch wie 5 je nach Anwendungsfall wiegen).
- 2 vektor fp mul oder FMA. (x * y + z ist so günstig wie mul oder add, wenn Sie mit aktivierter FMA-Unterstützung kompilieren).
- 2 Einfügen / Extrahieren von Allzweckregistern in Vektorelemente (
_mm_insert_epi8
usw.)
- 2,25 vector int mul (16-Bit-Elemente oder pmaddubsw tun 8 * 8 -> 16-Bit). Bei Skylake billiger, mit besserem Durchsatz als bei Scalar Mul
- 2,25 verschieben / drehen durch variable Anzahl (2 c Latenz, 1 pro 2 c Durchsatz bei Intel, schneller bei AMD oder mit BMI2)
- 2.5 Vergleich ohne Verzweigung (
y = x ? a : b
, oder y = x >= 0
) ( test / setcc
oder cmov
)
- 3 int-> float umwandlung
- 3 Perfekt vorhergesagter Kontrollfluss (vorhergesagte Verzweigung, Aufruf, Rückkehr).
- 4 vector int mul (32-bit elements) (2 uops, 10c latenz bei Haswell)
- 4 Ganzzahldivision oder
%
durch eine Konstante zur Kompilierungszeit (keine Potenz von 2).
- 7 horizontale Vektoroperationen (z. B.
PHADD
Hinzufügen von Werten innerhalb eines Vektors)
- 11 (Vektor) FP Division (10-13 c Latenz, einer pro 7 c Durchsatz oder schlechter). (Kann billig sein, wenn selten verwendet, aber der Durchsatz ist 6 bis 40x schlechter als bei FP mul)
- 13? Kontrollfluss (schlecht vorhergesagter Zweig, möglicherweise zu 75% vorhersehbar)
- 13 int division ( ja wirklich , es ist langsamer als die FP-Division und kann nicht vektorisieren). (Beachten Sie, dass Compiler durch eine Konstante mit mul / shift / add mit einer magischen Konstante dividieren und div / mod durch Potenzen von 2 sehr billig ist.)
- 16 (Vektor) FP sqrt
- 25? Laden (L3-Cache-Treffer). (Cache-Miss-Stores sind billiger als Ladungen.)
- 50? FP-Trigger / Exp / Log. Wenn Sie viel Exp / Log benötigen und nicht die volle Genauigkeit benötigen, können Sie Genauigkeit gegen Geschwindigkeit mit einem kürzeren Polynom und / oder einer Tabelle tauschen. Sie können auch SIMD vektorisieren.
- 50-80? Immer - unvorhergesehener Zweig, der 15-20 Zyklen kostet
- 200-400 & le; Laden / Speichern (Cache-Miss)
- 3000 ??? Seite aus Datei lesen (OS Disk Cache Hit) (Zahlen hier zusammenstellen)
- 20000 ??? Disk Read Page (OS-Disk-Cache-Fehler, schnelle SSD) (vollständig erfundene Nummer)
Ich habe das komplett durch Rätselraten erfunden . Wenn etwas falsch aussieht, liegt es entweder daran, dass ich an einen anderen Anwendungsfall gedacht habe, oder an einem Bearbeitungsfehler.
Die relativen Kosten für AMD-CPUs sind ähnlich, mit der Ausnahme, dass sie schnellere Integer-Shifter haben, wenn die Anzahl der Shifts variabel ist. CPUs der AMD Bulldozer-Familie sind auf den meisten Codes aus verschiedenen Gründen natürlich langsamer. (Ryzen ist ziemlich gut in vielen Dingen).
Denken Sie daran, dass es wirklich unmöglich ist, Dinge auf eindimensionale Kosten zu reduzieren . Abgesehen von Cachefehlern und Verzweigungsfehlern kann der Engpass in einem Codeblock die Latenz, der gesamte UOP-Durchsatz (Frontend) oder der Durchsatz eines bestimmten Ports (Ausführungsport) sein.
Eine "langsame" Operation wie die FP-Division kann sehr billig sein, wenn der umgebende Code die CPU mit anderen Arbeiten beschäftigt . (Vektor-FP-Div oder -SQRT sind jeweils 1 UOP, sie haben nur eine schlechte Latenz und einen schlechten Durchsatz. Sie blockieren nur die Divisionseinheit, nicht den gesamten Ausführungsport, auf dem sie sich befindet. Integer-Div sind mehrere UOPs.) Wenn Sie also nur eine FP-Divide haben für jeden ~ 20 mul und add, und es gibt andere arbeit für die CPU zu erledigen (zB eine unabhängige schleifeniteration), dann könnten die "kosten" des FP div ungefähr die gleichen sein wie bei einem FP mul. Dies ist wahrscheinlich das beste Beispiel für etwas, das nur einen geringen Durchsatz aufweist, sich aber aufgrund der geringen Gesamt-Uops sehr gut mit anderem Code vermischt (wenn die Latenz kein Faktor ist).
Beachten Sie, dass die Ganzzahldivision dem umgebenden Code bei weitem nicht so nahe kommt: In Haswell sind es 9 Uops mit einem Durchsatz von 8 bis 11 c und einer Latenz von 22 bis 29 c. (Die 64-Bit-Teilung ist selbst bei Skylake viel langsamer.) Die Latenz und die Durchsatzzahlen sind also ähnlich wie bei FP Div, aber FP Div ist nur ein UOP.
Beispiele zum Analysieren einer kurzen Sequenz von Insns auf Durchsatz, Latenz und Gesamt-Uops finden Sie in einigen meiner SO-Antworten:
- Der Abschnitt "Leistungsanalyse" dieser Antwort fasst die Dinge zusammen. In der restlichen Antwort geht es darum, eine Schleife zu optimieren,
sum += x[i] * y[i]
indem mit mehreren Vektorakkumulatoren abgewickelt wird, um die FMA-Latenz zu verbergen. Es ist ziemlich technisch und auf niedrigem Niveau, zeigt Ihnen jedoch, welche Art von Assembler-Ausgabe Sie von Ihrem Compiler erstellen lassen möchten und warum dies wichtig ist.
- Warum ist dieser C ++ - Code schneller als meine handgeschriebene Assembly zum Testen der Collatz-Vermutung? : Diese beliebte Antwort, die ich geschrieben habe, erklärt, wie man den Compiler in die Hand nimmt, um besseres Asm zu machen, wenn es möglich ist. Außerdem einige ASM-Optimierungsdetails, mit denen Sie den Compiler für kleine Funktionen / Schleifen in diesem Fall übertreffen können. IDK, warum es so viel mehr positive Stimmen gibt als meine anderen Antworten.
- Wie können gesetzte Bits an einer Position oder darunter effizient gezählt werden? : Eine Perf-Analyse einer Sequenz von 6 Insns für ein interessantes Problem, bei dem ein Handgriff in der C-Quelle dazu führte, dass gcc besseren Code erstellte. Einige meiner anderen Antworten beziehen sich auf noch kürzere Folgen von Anweisungen.
- Schnellster Absolutwertrechner mit SSE
- Probleme mit ADC / SBB und INC / DEC in engen Schleifen auf einigen CPUs
- Schnelle vektorisierte rsqrt und wechselseitig mit SSE / AVX je nach Präzision
- 64-Bit-Strukturen mit AVX sortieren?
- /programming//search?q=user%3A224132+throughput+latency+cycles
IDK, wenn andere SO Antworten einschließlich dieser Art von Analyse schreiben. Es fällt mir viel leichter, mein eigenes zu finden, weil ich weiß, dass ich oft auf dieses Detail gehe und mich an das erinnere, was ich geschrieben habe.