SQRT * und DIV * sind die einzigen zwei "einfachen" ALU-Befehle (Single UOP, keine mikrocodierte Verzweigung / Schleife), die auf modernen Intel / AMD-CPUs einen datenabhängigen Durchsatz oder eine datenabhängige Latenz aufweisen. (Das Nichtzählen des Mikrocodes hilft bei denormalen oder subnormalen FP-Werten in add / multiplizieren / fma). Alles andere ist so gut wie repariert, so dass die nicht in Ordnung befindlichen UOP-Planungsmaschinen nicht auf die Bestätigung warten müssen, dass ein Ergebnis in einem Zyklus fertig war, sondern nur wissen, dass dies der Fall sein wird.
Wie üblich bietet der Intrinsics-Leitfaden von Intel ein stark vereinfachtes Bild der Leistung. Die tatsächliche Latenz beträgt bei Skylake keine festen 18 Zyklen für doppelte Genauigkeit. (Aufgrund der Zahlen, die Sie zitiert haben, gehe ich davon aus, dass Sie einen Skylake haben.)
div / sqrt sind schwer zu implementieren; Selbst bei Hardware ist das Beste, was wir tun können, ein iterativer Verfeinerungsprozess. Wenn Sie mehr Bits gleichzeitig verfeinern (Radix-1024-Teiler seit Broadwell), wird dies beschleunigt (siehe diese Fragen und Antworten zur Hardware ). Es ist jedoch immer noch langsam genug, dass ein Early-Out verwendet wird, um einfache Fälle zu beschleunigen (oder der Beschleunigungsmechanismus überspringt nur einen Einrichtungsschritt für Mantissen mit Null auf modernen CPUs mit teilweise Pipeline-Div / SQL-Einheiten. Ältere CPUs hatten Durchsatz = Latenz für FP div / sqrt; diese Ausführungseinheit ist schwieriger zu leiten.)
https://www.uops.info/html-instr/VSQRTSD_XMM_XMM_XMM.html zeigt, dass Skylake SQRTSD zwischen 13 und 19 Latenzzeiten variieren kann. Die SKL (Client) -Nummern zeigen nur eine Latenz von 13 Zyklen, aber wir können der detaillierten SKL vsqrtsd-Seite entnehmen, dass sie nur mit Eingabe = 0 getestet wurden. SKX (Server) -Nummern zeigen eine Latenz von 13-19 Zyklen. ( Diese Seite enthält eine detaillierte Aufschlüsselung des verwendeten Testcodes, einschließlich der binären Bitmuster für die Tests.) Ähnliche Tests (mit nur 0 für Client-Kerne) wurden auf der Nicht-VEX-sqrtsd xmm, xmm
Seite durchgeführt. : /
InstLatx64- Ergebnisse zeigen Best- / Worst-Case-Latenzen von 13 bis 18 Zyklen auf Skylake-X (das den gleichen Kern wie Skylake-Client verwendet, jedoch mit aktiviertem AVX512).
Die Anweisungstabellen von Agner Fog zeigen die Latenz von 15-16 Zyklen auf Skylake. (Agner testet normalerweise mit einer Reihe unterschiedlicher Eingabewerte.) Seine Tests sind weniger automatisiert und stimmen manchmal nicht genau mit anderen Ergebnissen überein.
Was macht manche Fälle schnell?
Beachten Sie, dass die meisten ISAs (einschließlich x86) binäre Gleitkommazahlen verwenden :
Die Bits repräsentieren Werte als linearen Signifikanten (auch bekannt als Mantisse) mal 2 exp und als Vorzeichenbit.
Es scheint, dass es unter modernen Intel möglicherweise nur zwei Geschwindigkeiten gibt (zumindest seit Haswell) (siehe Diskussion mit @harold in den Kommentaren), z. B. sind sogar Zweierpotenzen alle schnell, wie 0,25, 1, 4 und 16. Diese sind trivial Mantisse = 0x0 für 1,0. https://www.h-schmidt.net/FloatConverter/IEEE754.html hat einen schönen interaktiven dezimalen <-> Bitmusterkonverter für einfache Genauigkeit mit Kontrollkästchen für die gesetzten Bits und Anmerkungen dessen, was die Mantisse und der Exponent darstellen.
Auf Skylake sind die einzigen schnellen Fälle, die ich bei einer schnellen Überprüfung gefunden habe, sogar Potenzen von 2 wie 4.0, aber nicht 2.0. Diese Zahlen haben ein genaues sqrt-Ergebnis, wobei sowohl die Eingabe als auch die Ausgabe eine Mantisse von 1,0 haben (nur die implizite 1-Bit-Menge). 9.0
ist nicht schnell, obwohl es genau darstellbar ist und das 3.0
Ergebnis auch. 3.0 hat Mantisse = 1,5, wobei nur das höchstwertige Bit der Mantisse in der binären Darstellung gesetzt ist. Die Mantisse von 9.0 ist 1.125 (0b00100 ...). Die Nicht-Null-Bits befinden sich also sehr nahe am oberen Rand, aber anscheinend reicht dies aus, um sie zu disqualifizieren.
( +-Inf
und NaN
sind auch schnell. Ebenso gewöhnliche negative Zahlen: result = -NaN . Ich messe die Latenz von 13 Zyklen für diese auf i7-6700k, genau wie für 4.0
. vs. 18 Latenzzeiten von 18 Zyklen für den langsamen Fall.)
x = sqrt(x)
ist definitiv schnell mit x = 1.0
(All-Null-Mantisse mit Ausnahme des impliziten führenden 1-Bit). Es hat eine einfache Eingabe und eine einfache Ausgabe.
Mit 2.0 ist die Eingabe ebenfalls einfach (All-Null-Mantisse und Exponent 1 höher), aber die Ausgabe ist keine runde Zahl. sqrt (2) ist irrational und hat daher unendlich viele Nicht-Null-Bits in jeder Basis. Dies macht es anscheinend langsam auf Skylake.
Die Befehlstabellen von Agner Fog besagen, dass die ganzzahlige div
Befehlsleistung von AMD K10 von der Anzahl der signifikanten Bits in der Dividende (Eingabe) und nicht vom Quotienten abhängt. Bei der Suche in den PDF- und Befehlstabellen von Agner wurden jedoch keine Fußnoten oder Informationen darüber gefunden, wie sqrt spezifisch ist datenabhängig.
Bei älteren CPUs mit noch langsamerem FP-Quadrat ist möglicherweise mehr Platz für eine Reihe von Geschwindigkeiten vorhanden. Ich denke, die Anzahl der signifikanten Bits in der Mantisse der Eingabe wird wahrscheinlich relevant sein. Weniger signifikante Bits (mehr nachfolgende Nullen im Signifikanten) machen es schneller, wenn dies korrekt ist. Aber auch hier scheinen auf Haswell / Skylake die einzigen schnellen Fälle gerade Potenzen von 2 zu sein.
Sie können dies mit etwas testen , das die Ausgabe wieder mit der Eingabe koppelt, ohne die Datenabhängigkeit zu unterbrechen , z. B. andps xmm0, xmm1
/ orps xmm0, xmm2
, um einen festen Wert in xmm0 festzulegen, der von der sqrtsd-Ausgabe abhängt.
Oder eine einfachere Möglichkeit, die Latenz zu testen, besteht darin, die falsche Ausgabeabhängigkeit vonsqrtsd xmm0, xmm1
- it "auszunutzen" und sqrtss
die oberen 64/32 Bits (bzw.) des Ziels unverändert zu lassen, sodass das Ausgaberegister auch eine Eingabe für diese Zusammenführung ist. Ich gehe davon aus, dass Ihr naiver Inline-Asm-Versuch auf diese Weise zu einem Engpass bei der Latenz statt zum Durchsatz führte, wobei der Compiler ein anderes Register für die Ausgabe auswählte, sodass er dieselbe Eingabe in einer Schleife erneut lesen konnte. Die Inline - asm Sie zu Ihrer Frage hinzugefügt wird total kaputt und wird nicht einmal kompilieren, aber vielleicht Ihr echter Code verwendet "x"
(XMM - Register) Eingangs- und Ausgangsbeschränkungen statt "i"
(sofort)?
Diese NASM-Quelle für eine statische ausführbare Testschleife (unter der ausgeführt werden soll perf stat
) verwendet diese falsche Abhängigkeit mit der Nicht-VEX-Codierung von sqrtsd
.
Diese ISA-Designwarze ist Intel zu verdanken, das kurzfristig mit SSE1 auf Pentium III optimiert hat. P3 behandelte 128-Bit-Register intern als zwei 64-Bit-Hälften. Wenn Sie die obere Hälfte unverändert lassen, lassen Sie skalare Anweisungen zu einem einzigen UOP dekodieren. (Aber das gibt PIII immer noch sqrtss
eine falsche Abhängigkeit). Mit AVX können wir dies schließlich vsqrtsd dst, src,src
zumindest für Registerquellen und ähnlich vcvtsi2sd dst, cold_reg, eax
für die ähnlich kurzsichtig gestalteten skalaren Int-> Fp-Konvertierungsanweisungen vermeiden. (GCC verpasste-Optimierung Berichte: 80586 , 89071 , 80571 ) .
Bei vielen früheren CPUs war sogar der Durchsatz variabel, aber Skylake hat die Teiler so stark verbessert, dass der Scheduler immer weiß, dass er nach der letzten Eingabe mit einfacher Genauigkeit einen neuen div / sqrt bis 3 Zyklen starten kann.
Sogar der Skylake-Durchsatz mit doppelter Genauigkeit ist variabel: 4 bis 6 Zyklen nach der letzten Eingabe mit doppelter Genauigkeit, wenn die Anweisungstabellen von Agner Fog richtig sind.
https://uops.info/ zeigt einen flachen wechselseitigen 6c-Durchsatz. (Oder doppelt so lang für 256-Bit-Vektoren; 128-Bit und Skalar können separate Hälften der breiten SIMD-Teiler für mehr Durchsatz bei gleicher Latenz verwenden.) Siehe auch Gleitkommadivision vs. Gleitkommamultiplikation für einige extrahierte Durchsatz- / Latenzzahlen aus den Anweisungstabellen von Agner Fog.