Warum ändert sich die Latenz des Befehls sqrtsd basierend auf der Eingabe? Intel-Prozessoren

9

Nun, im Intel Intrinsic Guide wird angegeben, dass der Befehl "sqrtsd" eine Latenz von 18 Zyklen hat.

Ich habe es mit meinem eigenen Programm getestet und es ist richtig, wenn wir zum Beispiel 0,15 als Eingabe nehmen. Aber wenn wir 256 (oder eine beliebige 2 ^ x) Zahl nehmen, beträgt die Latenz nur 13. Warum ist das so?

Eine Theorie, die ich hatte, ist, dass da 13 die Latenz von "sqrtss" ist, die mit "sqrtsd" identisch ist, aber mit 32-Bit-Gleitkommawerten durchgeführt wurde, der Prozessor möglicherweise klug genug war, um zu verstehen, dass 256 in 32-Bit passen und daher diese Version verwenden können während 0,15 das volle 64-Bit benötigt, da es nicht auf endliche Weise darstellbar ist.

Ich mache es mit Inline-Assembly, hier ist der Relveant-Teil, der mit gcc -O3 und -fno-tree-vectorize kompiliert wurde.

static double sqrtsd (double x) {
    double r;
    __asm__ ("sqrtsd %1, %0" : "=x" (r) : "x" (x));
    return r;
}
Tommy95
quelle
3
Zeigen Sie uns den Code des Tests. Ich kann mir eine Implementierung vorstellen, bei der die Optimierung vom Compiler und nicht vom Prozessor vorgenommen wird.
Robert Navado
Prozessoren sind nicht schlau: Sie führen die gegebenen Anweisungen aus.
Wetterfahne
Beantwortet das deine Frage? Warum ist SSE skalar sqrt (x) langsamer als rsqrt (x) * x?
Tarek Dakhran
2
Sie stellen sich keine Dinge vor: instlatx64 für skylake listet auch 18 (Worst Case) und 13 (einfache Werte) auf
Harold
1
Ihr Inline-Asm macht keinen Sinn und wird nicht kompiliert: godbolt.org/z/rJA6nS . "i"Gibt an, dass es sich um eine sofortige und keine Ausgabebeschränkung handelt. sqrtsdAkzeptiert nur eine reg / mem-Eingabe, nicht sofort, würde sich also nicht zusammensetzen, selbst wenn es kompiliert würde. Wenn Sie sofort die Konstante für die Kompilierungszeit verwenden, können Sie die Latenz nicht testen, sondern nur den Durchsatz. Aber Ihre Zahlen sehen vernünftig aus. Was auch immer Sie tatsächlich getan haben, hat wahrscheinlich die sqrtsd-Latenz getestet.
Peter Cordes

Antworten:

10

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.0ist nicht schnell, obwohl es genau darstellbar ist und das 3.0Ergebnis 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.

( +-Infund NaNsind 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 divBefehlsleistung 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 sqrtssdie 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 sqrtsseine falsche Abhängigkeit). Mit AVX können wir dies schließlich vsqrtsd dst, src,srczumindest für Registerquellen und ähnlich vcvtsi2sd dst, cold_reg, eaxfü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.

Peter Cordes
quelle
Was ist übrigens mit den Latenzen zwischen den beiden Extremen? Passieren sie? Ich konnte es auf meinem Haswell nicht schaffen, aber das ist nicht schlüssig
Harold
@harold: IDK, ich würde vermuten, dass es, wenn es möglich wäre, mit einer geringeren Anzahl von nachgestellten Nullen in der Mantisse passieren würde. Aber vielleicht gibt es nur einen Sonderfall-Frühdetektor für die einfachsten Fälle. Haswells Teiler mit niedrigerem Radix sollte es rentabler machen, früher nach einem frühen Out zu suchen, aber vielleicht ist es eine Frage der anfänglichen Schätzung (aus derselben Tabelle, die rsqrt verwendet), ob sie genau ist oder nicht, und wenn nicht, muss sie iterativ verfeinert werden Weg zum Ende.
Peter Cordes
rsqrtist zwar nicht genau für Zweierpotenzen (auf Haswell sowieso), aber Zweierpotenzen und Nullen sind bis jetzt die einzigen Eingaben, die ich gefunden habe, bei denen die Quadratwurzel schnell ist. rsqrtAndererseits scheinen die Anweisungen mehr zu tun als nur eine Suche wie lange ihre Latenz tatsächlich ist
Harold
@harold: ist rsqrtmöglicherweise nicht die Rohausgabe der LUT (ja, wie Sie bearbeitet haben, ist die hohe Latenz möglicherweise etwas Arbeit). Oder vielleicht führt es zur genauen Antwort für einfache Eingaben (All-Null-Mantisse). Oder vielleicht kann eine All-Null-Mantisse die LUT-Suche überspringen, bevor mit der Verfeinerung begonnen wird. Ich weiß nicht genug über HW-Teiler, um diese Vermutungen auszuschließen. : /
Peter Cordes
1
Ist sqrtsdschnell für Zweierpotenzen mit ungeraden Exponenten? Oder nur für Zweierpotenzen mit geraden Exponenten? Das ist interessant.
Fuz