In jeder Programmiersprache gibt es Opcode-Sätze, die anderen vorzuziehen sind. Ich habe versucht, sie hier in der Reihenfolge ihrer Geschwindigkeit aufzulisten.
- Bitweise
- Ganzzahlige Addition / Subtraktion
- Ganzzahlige Multiplikation / Division
- Vergleich
- Kontrollfluss
- Float Addition / Subtraktion
- Float-Multiplikation / Division
Wenn Sie leistungsstarken Code benötigen, kann C ++ in der Assembly von Hand optimiert werden, um SIMD-Anweisungen oder einen effizienteren Steuerungsfluss, Datentypen usw. zu verwenden. Ich versuche also zu verstehen, ob der Datentyp (int32 / float32 / float64) oder der Betrieb verwendet wird ( *
, +
, &
) beeinflusst die Leistung auf CPU - Ebene.
- Ist eine einzelne Multiplikation auf der CPU langsamer als eine Addition?
- In der MCU-Theorie lernen Sie, dass die Geschwindigkeit von Opcodes durch die Anzahl der CPU-Zyklen bestimmt wird, die zur Ausführung erforderlich sind. Bedeutet dies, dass das Multiplizieren 4 Zyklen und das Addieren 2 Zyklen dauert?
- Was genau sind die Geschwindigkeitsmerkmale der grundlegenden mathematischen und Kontrollfluss-Opcodes?
- Wenn zwei Opcodes die gleiche Anzahl von Zyklen benötigen, um ausgeführt zu werden, können beide ohne Leistungsgewinn / -verlust austauschbar verwendet werden.
- Alle anderen technischen Details, die Sie zur x86-CPU-Leistung mitteilen können, sind willkommen
c++
performance
optimization
Robinicks
quelle
quelle
Antworten:
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.)
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.
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.
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.)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 .
Verschieben und Drehen ( konstante Anzahl zur Kompilierungszeit) /
Vektorversionen von all diesen (1 bis 4 pro Zyklusdurchsatz, 1 Zykluslatenz )
tmp += 7
eine Schleife reduziert werden anstatttmp = i*7
)sum
Variablen summieren . (Ich könnte dies und fp mul so niedrig wie 1 oder so hoch wie 5 je nach Anwendungsfall wiegen)._mm_insert_epi8
usw.)y = x ? a : b
, odery = x >= 0
) (test / setcc
odercmov
)%
durch eine Konstante zur Kompilierungszeit (keine Potenz von 2).PHADD
Hinzufügen von Werten innerhalb eines Vektors)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:
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.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.
quelle
Das hängt von der jeweiligen CPU ab, aber für eine moderne CPU sieht die Liste ungefähr so aus:
Abhängig von der CPU kann das Arbeiten mit 64-Bit-Datentypen erhebliche Kosten verursachen.
Deine Fragen:
if
was Sie mit Arithmetik vernünftigerweise tun können.Und schließlich, wenn Sie ein Spiel machen, sorgen Sie sich nicht zu sehr darum, sondern konzentrieren Sie sich lieber darauf, ein gutes Spiel zu machen, als die CPU-Zyklen zu unterbrechen.
quelle
Ich habe einen Test über Integer-Operationen durchgeführt, der millionenfach auf x64_64 geloopt wurde.
addieren --- 116 Mikrosekunden
Sub ---- 116 Mikrosekunden
mul ---- 1036 Mikrosekunden
div ---- 13037 Mikrosekunden
Die obigen Daten haben bereits den durch die Schleife verursachten Overhead verringert.
quelle
Die Intel-Prozessorhandbücher können kostenlos von der Website heruntergeladen werden. Sie sind ziemlich groß, können aber technisch Ihre Frage beantworten. Insbesondere das Optimierungshandbuch ist genau das, wonach Sie suchen. In der Bedienungsanleitung sind jedoch auch die Timings und Latenzen für die meisten wichtigen CPU-Linien für einfache Anweisungen aufgeführt, da sie von Chip zu Chip variieren.
Im Allgemeinen würde ich sowohl vollständige Zweige als auch Pointer-Chasing (Link-List-Traverals, Aufrufen virtueller Funktionen) als Top-Performance-Killer betrachten, aber die x86 / x64-CPUs sind in beiden Bereichen im Vergleich zu anderen Architekturen sehr gut. Wenn Sie jemals auf eine andere Plattform portieren, werden Sie feststellen, wie groß das Problem sein kann, wenn Sie Hochleistungscode schreiben.
quelle