Alle folgenden Anweisungen machen dasselbe: %eax
auf Null setzen. Welcher Weg ist optimal (erfordert nur wenige Maschinenzyklen)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
performance
assembly
optimization
x86
micro-optimization
balajimc55
quelle
quelle
Antworten:
TL; DR-Zusammenfassung :
xor same, same
ist die beste Wahl für alle CPUs . Keine andere Methode hat einen Vorteil gegenüber dieser, und sie hat zumindest einen Vorteil gegenüber jeder anderen Methode. Es wird offiziell von Intel und AMD empfohlen und was Compiler tun. Verwenden Sie im 64-Bit-Modus weiterhinxor r32, r32
, da das Schreiben eines 32-Bit-Registers die oberen 32 auf Null setzt .xor r64, r64
ist eine Verschwendung von Bytes, da es ein REX-Präfix benötigt.Schlimmer noch, Silvermont erkennt nur
xor r32,r32
als dep-brechend, nicht als 64-Bit-Operandengröße. So , selbst wenn ein Präfix REX ist weiterhin erforderlich , weil Sie Nullstellung r8..r15, verwendenxor r10d,r10d
, nichtxor r10,r10
.GP-Integer-Beispiele:
Das Nullstellen eines Vektorregisters ist normalerweise am besten mit
pxor xmm, xmm
. Dies ist normalerweise das, was gcc tut (noch vor der Verwendung mit FP-Anweisungen).xorps xmm, xmm
kann Sinn machen. Es ist ein Byte kürzer alspxor
,xorps
benötigt jedoch Ausführungsport 5 unter Intel Nehalem undpxor
kann an jedem Port (0/1/5) ausgeführt werden. (Die 2c-Bypass-Verzögerungslatenz von Nehalem zwischen Ganzzahl und FP ist normalerweise nicht relevant, da die Ausführung außerhalb der Reihenfolge sie normalerweise zu Beginn einer neuen Abhängigkeitskette verbergen kann.)Bei Mikroarchitekturen der SnB-Familie benötigt keine Xor-Zeroing-Variante sogar einen Ausführungsport. Auf AMD und Pre-Nehalem P6 / Core2 Intel,
xorps
undpxor
werden auf die gleiche Art und Weise (als Vektor-Integer - Befehle) behandelt.Wenn Sie die AVX-Version eines 128b-Vektorbefehls verwenden, wird auch der obere Teil der Registrierung auf Null gesetzt. Dies
vpxor xmm, xmm, xmm
ist eine gute Wahl, um YMM (AVX1 / AVX2) oder ZMM (AVX512) oder eine zukünftige Vektorerweiterung auf Null zu setzen.vpxor ymm, ymm, ymm
Zum Codieren werden jedoch keine zusätzlichen Bytes benötigt, und unter Intel wird dasselbe ausgeführt, unter AMD jedoch langsamer als vor Zen2 (2 Uops). Das ZXM-Nullstellen von AVX512 würde zusätzliche Bytes erfordern (für das EVEX-Präfix), daher sollte das Nullstellen von XMM oder YMM bevorzugt werden.XMM / YMM / ZMM-Beispiele
Siehe Ist vxorps-zeroing bei AMD Jaguar / Bulldozer / Zen mit xmm-Registern schneller als mit ymm? und
Was ist der effizienteste Weg, um ein oder mehrere ZMM-Register auf Knights Landing zu löschen?
Semi-related: Der schnellste Weg, den __m256-Wert auf alle ONE-Bits zu setzen und
alle Bits im CPU-Register effizient auf 1 zu setzen, deckt auch AVX512-Maskenregister ab
k0..7
. SSE / AVXvpcmpeqd
ist bei vielen abhängig (obwohl zum Schreiben der 1 noch ein UOP erforderlich ist), aber AVX512vpternlogd
für ZMM-Regs ist nicht einmal abhängig . Innerhalb einer Schleife sollten Sie in Betracht ziehen, aus einem anderen Register zu kopieren, anstatt diese mit einem ALU-UOP neu zu erstellen, insbesondere mit AVX512.Das Nullstellen ist jedoch billig: Das xor-Nullstellen eines xmm-Regs innerhalb einer Schleife ist normalerweise so gut wie das Kopieren, außer bei einigen AMD-CPUs (Bulldozer und Zen), die eine Bewegungseliminierung für Vektorregs aufweisen, aber dennoch ein ALU-Uop benötigen, um Nullen für xor zu schreiben Null.
Was ist das Besondere daran, Redewendungen wie xor auf verschiedenen Uarchen auf Null zu setzen?
Einige CPUs erkennen
sub same,same
als Null-Idiomxor
, aber alle CPUs, die Null-Idiome erkennen, erkennenxor
. Verwendenxor
Sie es einfach, damit Sie sich keine Gedanken darüber machen müssen, welche CPU welches Null-Idiom erkennt.xor
(im Gegensatz zu einer anerkannten Null-Sprachemov reg, 0
) hat einige offensichtliche und einige subtile Vorteile (zusammenfassende Liste, dann werde ich diese erweitern):mov reg,0
. (Alle CPUs)Eine kleinere Maschinencodegröße (2 Bytes statt 5) ist immer von Vorteil: Eine höhere Codedichte führt zu weniger Befehls-Cache-Fehlern und einem besseren Befehlsabruf und einer möglichen Dekodierung der Bandbreite.
Der Vorteil, keine Ausführungseinheit für xor in Mikroarchitekturen der Intel SnB-Familie zu verwenden, ist gering, spart jedoch Strom. Es ist wahrscheinlicher, dass SnB oder IvB eine Rolle spielen, die nur 3 ALU-Ausführungsports haben. Haswell und höher verfügen über 4 Ausführungsports, die ganzzahlige ALU-Anweisungen verarbeiten können.
mov r32, imm32
Mit einer perfekten Entscheidungsfindung durch den Scheduler (was in der Praxis nicht immer der Fall ist) kann HSW auch dann noch 4 Uops pro Takt aufrechterhalten, wenn alle ALU benötigen Ausführungsports.Weitere Einzelheiten finden Sie in meiner Antwort auf eine andere Frage zum Nullstellen von Registern .
Bruce Dawsons Blog-Beitrag , den Michael Petch (in einem Kommentar zur Frage) verlinkt hat, weist darauf hin, dass er
xor
in der Phase des Umbenennens des Registers behandelt wird, ohne dass eine Ausführungseinheit erforderlich ist (null Uops in der nicht fusionierten Domäne), aber die Tatsache übersehen hat, dass es immer noch ein UOP ist in der fusionierten Domäne. Moderne Intel-CPUs können 4 UPs mit fusionierter Domäne pro Takt ausgeben und außer Betrieb setzen. Daher kommt die Grenze von 4 Nullen pro Takt. Die erhöhte Komplexität der Hardware zum Umbenennen von Registern ist nur einer der Gründe, die Breite des Designs auf 4 zu beschränken. (Bruce hat einige sehr gute Blog-Beiträge verfasst, wie seine Serie zu FP-Mathematik und x87 / SSE / Rundungsproblemen , die ich mache sehr empfehlenswert).Auf AMD Bulldozer-Familie CPUs ,
mov immediate
läuft auf demselben EX0 / EX1 Integer - Ausführungs Portsxor
.mov reg,reg
kann auch auf AGU0 / 1 ausgeführt werden, dies gilt jedoch nur zum Kopieren von Registern, nicht zum Festlegen von Sofortnachrichten. AFAIK, bei AMD ist der einzige Vorteilxor
gegenübermov
der kürzeren Codierung. Es könnte auch physische Registerressourcen sparen, aber ich habe keine Tests gesehen.Anerkannte Nullstellungssprachen vermeiden Teilregisterstrafen auf Intel-CPUs, die Teilregister getrennt von Vollregistern (P6- und SnB-Familien) umbenennen.
xor
wird das Register Tag , das die oberen Teile auf Null gesetzt haben , soxor eax, eax
/inc al
/inc eax
vermeidet die üblichen Teilregister Strafe , die pre-IVB CPUs haben. Auch ohnexor
benötigt IvB nur dann ein Zusammenführen, wenn die hohen 8 Bit (AH
) geändert werden und dann das gesamte Register gelesen wird, und Haswell entfernt dies sogar.Aus dem Microarch-Handbuch von Agner Fog, S. 98 (Abschnitt Pentium M, auf den in späteren Abschnitten einschließlich SnB verwiesen wird):
pg82 diese Führung auch bestätigt , dass
mov reg, 0
ist nicht als Nullstellung Idiom anerkannt, zumindest auf frühen P6 - Designs wie PIII oder PM. Ich wäre sehr überrascht, wenn sie Transistoren für die Erkennung auf späteren CPUs ausgeben würden.xor
setzt Flags , was bedeutet, dass Sie beim Testen der Bedingungen vorsichtig sein müssen. Da diessetcc
leider nur mit einem 8-Bit-Ziel verfügbar ist , müssen Sie normalerweise darauf achten, dass Teilregistrierungsstrafen vermieden werden.Es wäre schön gewesen, wenn x86-64 einen der entfernten Opcodes (wie AAM) für ein 16/32
setcc r/m
/ 64-Bit umfunktioniert hätte , wobei das Prädikat im 3-Bit-Feld des Quellregisters des R / M-Felds (übrigens) codiert wäre Einige andere Einzeloperandenbefehle verwenden sie als Opcode-Bits. Aber das haben sie nicht getan, und das würde für x86-32 sowieso nicht helfen.Idealerweise sollten Sie
xor
/ set flags /setcc
/ read full register verwenden:Dies hat eine optimale Leistung auf allen CPUs (keine Blockierungen, Zusammenführen von Uops oder falsche Abhängigkeiten).
Die Dinge sind komplizierter, wenn Sie nicht vor einer Anweisung zum Setzen von Flags xor möchten . Beispiel: Sie möchten unter denselben Bedingungen auf eine Bedingung verzweigen und dann auf eine andere Bedingung setzen. zB
cmp/jle
,sete
und Sie haben entweder kein Ersatzregister oder Sie möchtenxor
den nicht genommenen Codepfad ganz ausschließen.Es gibt keine erkannten Nullpunkt-Idiome, die keine Auswirkungen auf Flags haben. Die beste Wahl hängt daher von der Ziel-Mikroarchitektur ab. Auf Core2 kann das Einfügen eines zusammengeführten Uops zu einem Stillstand von 2 oder 3 Zyklen führen. Bei SnB scheint es billiger zu sein, aber ich habe nicht viel Zeit damit verbracht, zu messen. Die Verwendung von
mov reg, 0
/setcc
hätte bei älteren Intel-CPUs erhebliche Nachteile und wäre bei neueren Intel-Geräten noch etwas schlechter.Die Verwendung von
setcc
/movzx r32, r8
ist wahrscheinlich die beste Alternative für Intel P6- und SnB-Familien, wenn Sie vor dem Flag-Setting-Befehl nicht xor-zero können. Das sollte besser sein, als den Test nach einem Xor-Zeroing zu wiederholen. (Nicht einmal überlegensahf
/lahf
oderpushf
/popf
). IvB kann eliminierenmovzx r32, r8
(dh mit Registerumbenennung ohne Ausführungseinheit oder Latenz wie xor- zeroing umgehen). Haswell und später eliminieren nur reguläremov
Anweisungen, nehmen alsomovzx
eine Ausführungseinheit und haben eine Latenz ungleich Null, was test /setcc
/movzx
schlechter alsxor
/ test / machtsetcc
, aber immer noch mindestens so gut wie test /mov r,0
/setcc
(und viel besser auf älteren CPUs).Die Verwendung von
setcc
/movzx
ohne Nullung ist bei AMD / P4 / Silvermont schlecht, da Deps für Unterregister nicht separat verfolgt werden. Es würde eine falsche Abhängigkeit vom alten Wert des Registers geben. Die Verwendung vonmov reg, 0
/setcc
zum Nullstellen / Unterbrechen von Abhängigkeiten ist wahrscheinlich die beste Alternative, wennxor
/ test /setcc
keine Option ist.Wenn die
setcc
Ausgabe nicht breiter als 8 Bit sein soll, müssen Sie natürlich nichts auf Null setzen. Achten Sie jedoch auf falsche Abhängigkeiten von anderen CPUs als P6 / SnB, wenn Sie ein Register auswählen, das kürzlich Teil einer langen Abhängigkeitskette war. (Und seien Sie vorsichtig, wenn Sie eine Funktion aufrufen, mit der das Register, in dem Sie einen Teil verwenden, gespeichert / wiederhergestellt werden kann.)and
mit einer unmittelbaren Null ist nicht speziell unabhängig von dem alten Wert auf allen mir bekannten CPUs, sodass die Abhängigkeitsketten nicht unterbrochen werden. Es hat keine Vor-xor
und Nachteile.Es ist nur sinnvoll für Microbenchmarks zu schreiben , wenn Sie wollen eine Abhängigkeit als Teil einer Latenztest, sondern wollen durch Nullsetzen und das Hinzufügen eines bekannten Wert schaffen.
Unter http://agner.org/optimize/ finden Sie Details zu Mikroarch , einschließlich der Frage, welche Nullpunkt-Idiome als Abhängigkeitsunterbrechung erkannt werden (z. B.
sub same,same
bei einigen, aber nicht bei allen CPUs, währendxor same,same
sie bei allen erkannt werden).mov
Dadurch wird die Abhängigkeitskette für den alten Wert unterbrochen des Registers (unabhängig vom Quellwert, Null oder nicht, denn somov
funktioniert es).xor
Unterbricht Abhängigkeitsketten nur in dem Sonderfall, in dem src und dest dasselbe Register sind, weshalbmov
sie in der Liste der speziell erkannten Abhängigkeitsunterbrecher nicht aufgeführt sind. (Auch, weil es nicht als Null-Redewendung erkannt wird, mit den anderen Vorteilen, die sich daraus ergeben.)Interessanterweise ist die älteste P6 - Design (PPro durch Pentium III) nicht erkennen
xor
als Abhängigkeit Brecher -zeroing, nur als Nullstellung Idiom für die Zwecke der Vermeidung von Teilregisterstände , so dass in einigen Fällen war es lohnt sich der Einsatz beidemov
und dannxor
- Null in dieser Reihenfolge, um die Dep zu brechen und dann wieder Null zu setzen + Setzen Sie das interne Tag-Bit so, dass die High-Bits Null sind, also EAX = AX = AL.Siehe Beispiel 6.17 von Agner Fog. in seinem Mikroarch pdf. Er sagt, dass dies auch für P2, P3 und sogar (früh?) PM gilt. Ein Kommentar zu dem verlinkten Blog-Beitrag besagt, dass nur PPro dieses Versehen hatte, aber ich habe auf Katmai PIII und @Fanael auf einem Pentium M getestet, und wir haben beide festgestellt, dass es keine Abhängigkeit für eine Latenz aufhebt -gebundene
imul
Kette. Dies bestätigt leider die Ergebnisse von Agner Fog.TL: DR:
Wenn es Ihren Code wirklich schöner macht oder Anweisungen speichert, sollten Sie auf Null setzen
mov
, um ein Berühren der Flags zu vermeiden, solange Sie kein anderes Leistungsproblem als die Codegröße einführen. Das Vermeiden von Clobbering-Flags ist der einzig vernünftige Grund für die Nichtverwendungxor
, aber manchmal können Sie xor-zero vor dem Ding setzen, das Flags setzt, wenn Sie ein Ersatzregister haben.mov
-zero vorsetcc
ist für die Latenz besser alsmovzx reg32, reg8
nach (außer bei Intel, wenn Sie verschiedene Register auswählen können), aber schlechtere Codegröße.quelle
mov reg, src
auch Dep-Ketten für OO-CPUs unterbrochen (unabhängig davon, ob src imm32[mem]
oder ein anderes Register ist). Dieser Abhängigkeitsbruch wird in Optimierungshandbüchern nicht erwähnt, da es sich nicht um einen Sonderfall handelt, der nur auftritt, wenn src und dest dasselbe Register sind. Es passiert immer für Anweisungen, die nicht von ihrem Ziel abhängen. (Mit Ausnahme von Intels Implementierungpopcnt/lzcnt/tzcnt
einer falschen Abhängigkeit vom Ziel)mov
frei, nur keine Latenz. Der Teil "keinen Ausführungsport nehmen" ist normalerweise nicht wichtig. Der Fused-Domain-Durchsatz kann leicht der Engpass sein, insb. mit Ladungen oder Speichern in der Mischung.xor r64, r64
vergeudet nicht nur ein Byte. Wie Sie sagen,xor r32, r32
ist die beste Wahl vor allem mit KNL. Weitere Informationen finden Sie in Abschnitt 15.7 "Sonderfälle der Unabhängigkeit" in diesem Mikrarch-Handbuch.