2015-11-12 9 views
74

Alle folgenden Anweisungen machen das Gleiche: %eax auf Null setzen. Welcher Weg ist optimal (möglichst wenige Maschinenzyklen)?Was ist der beste Weg, um ein Register in x86 Assembly auf Null zu setzen: xor, mov oder und?

xorl %eax, %eax 
mov $0, %eax 
andl $0, %eax 
+3

Vielleicht möchten Sie dies lesen [Artikel] (https://randomascii.wordpress.com/2012/12/29/the-surpriness-subletties-of-zeroing-a-register/) –

+1

xor vs mov: http : //stackoverflow.com/questions/1135679/does-using-xor-reg-reg-give-advantage-over-mov-reg-0 –

Antwort

148

TL; DR Zusammenfassung: xor same, same ist die beste Wahl für alle CPUs. Keine andere Methode hat einen Vorteil gegenüber dieser Methode, und sie hat zumindest einen Vorteil gegenüber jeder anderen Methode. Es wird offiziell von Intel und AMD empfohlen. Verwenden Sie im 64-Bit-Modus weiterhin xor r32, r32, da writing a 32-bit reg zeros the upper 32. xor r64, r64 ist eine Verschwendung von einem Byte, weil es ein REX-Präfix benötigt.

Das Nullsetzen eines Vektorregisters erfolgt normalerweise am besten mit pxor xmm, xmm. Das ist typisch, was gcc macht (sogar vor Verwendung mit FP-Anweisungen).

xorps xmm, xmm kann sinnvoll sein. Es ist ein Byte kürzer als pxor, aber xorps benötigt Ausführungsport 5 auf Intel Nehalem, während pxor auf jedem Port (0/1/5) ausgeführt werden kann. (Nehalems 2c-Bypass-Verzögerungslatenz zwischen Integer und FP ist normalerweise nicht relevant, da die Out-of-Order-Ausführung diese typischerweise am Anfang einer neuen Abhängigkeitskette verbergen kann).

Auf SnB-Familien-Mikroarchitekturen benötigt keiner der beiden Funktionen von xor-zeroing einen Ausführungsport. Auf AMD und vor Nehalem P6/Core2 Intel werden xorps und pxor auf die gleiche Weise behandelt (als Vektor-Integer-Anweisungen).

Die Verwendung der AVX-Version eines 128B-Vektorbefehls setzt den oberen Teil des Reg ebenfalls auf Null, so dass vpxor xmm, xmm, xmm eine gute Wahl zum Nullsetzen von YMM (AVX1/AVX2) oder ZMM (AVX512) oder einer zukünftigen Vektorerweiterung ist. vpxor ymm, ymm, ymm nimmt jedoch keine zusätzlichen Bytes zum Codieren und führt dasselbe aus. Das Nullsetzen des AVX512 ZMM würde zusätzliche Bytes erfordern (für das EVEX-Präfix), daher sollte XMM oder YMM-Nullsetzung bevorzugt werden.


Einige CPUs erkennen sub same,same als Nullstellung Idiom wie xor, aber alle CPUs, die alle Nullstellen Idiome xor erkennen erkennen. Verwenden Sie einfach xor, damit Sie sich keine Gedanken darüber machen müssen, welche CPU welches Nullsetzungs-Idiom erkennt.

xor (eine anerkannte Nullstellung Idiom, im Gegensatz zu mov reg, 0 ist) einige offensichtliche und einige subtile Vorteile hat (Zusammenfassung Liste, dann werde ich auf jene erweitern):

  • kleinere Codegröße als mov reg,0. (Alle CPUs)
  • vermeidet partielle Registerstrafen für späteren Code. (Intel P6-Familie und SnB-Familie).
  • verwendet keine Ausführungseinheit, spart Strom und gibt Ausführungsressourcen frei. (Intel SnB-Familie)
  • kleinerer UOP (keine unmittelbaren Daten) lässt Raum in der UOP-Cachelinie für nahe gelegene Anweisungen, um bei Bedarf zu leihen. (Intel SnB-Familie).
  • doesn't use up entries in the physical register file. (Intel-SnB-Familie (und P4) zumindest, möglicherweise auch AMD, da sie ein ähnliches PRF-Design verwenden, anstatt den Registerzustand in den ROB-ähnlichen Mikroarchitekturen der Intel P6-Familie beizubehalten.)

Kleinere Maschinencode Größe (2 Bytes statt 5) ist immer ein Vorteil: höhere Codedichte führt zu weniger Befehl Cache-Misses und besserer Befehlsabruf und möglicherweise Bandbreite dekodieren.


Der Vorteil der nicht eine Ausführungseinheit für xor auf Intel SnB-Familie mit Mikroarchitekturen ist gering, aber spart Strom. Es ist wahrscheinlicher, dass es auf SnB oder IvB ankommt, die nur 3 ALU-Ausführungsports haben. Haswell und später haben 4 Ausführungsports, die Integer-ALU-Anweisungen verarbeiten können, einschließlich mov r32, imm32, so dass HSW mit perfekten Entscheidungsfindungen durch den Scheduler (was in der Praxis nicht vorkommt) auch 4 Ups pro Takt aufrechterhalten kann, selbst wenn sie alle ausgeführt werden müssen Häfen.

Weitere Details finden Sie unter my answer on another question about zeroing registers.

Bruce Dawson's blog post, die Michael Petch (in einem Kommentar zu der Frage) verknüpft weist darauf hin, dass xor in dem Register-umbenennen Stufe behandelt wird, um eine Ausführungseinheit (Null Uops in der nicht fusionierten Domäne), ohne dass aber die Tatsache übersehen, dass es nach wie vor ist ein up in der fusionierten Domäne. Moderne Intel-CPUs können & ausgeben 4 Fused-Domain-Ups pro Takt abstellen. Von dort kommt die 4 Nullen pro Taktgrenze. Die erhöhte Komplexität der Registerumbenennungshardware ist nur einer der Gründe, die Breite des Designs auf 4 zu begrenzen. (Bruce hat einige sehr gute Blogposts geschrieben, wie seine Serie auf FP math and x87/SSE/rounding issues, die ich sehr empfehle).


auf AMD Bulldozer-Familie CPUs, mov immediate läuft auf demselben EX0/EX1 Integer-Ausführungs Ports als xor. mov reg,reg kann auch auf AGU0/1 laufen, aber das ist nur für das Kopieren von Registern, nicht für die Einstellung von Sofortnachrichten. So AFAIK, auf AMD der einzige Vorteil zu xor über mov ist die kürzere Codierung. Es könnte auch physische Registerressourcen sparen, aber ich habe keine Tests gesehen.


Anerkannte Nullung Idiome Teil registrieren vermeiden Strafen auf Intel-CPUs, die von der vollständigen Register Teilregister getrennt umbenennen (P6 & SnB Familien).

xor wird tag das Register als die oberen Teile genullt ist, so xor eax, eax/inc al/inc eax vermeidet die üblichen Teilregister Strafe, die pre-IVB CPUs haben. Sogar ohne xor, IvB braucht nur eine Zusammenführung von up, wenn die hohen 8bits (AH) geändert werden und dann das ganze Register gelesen wird, und Haswell entfernt sogar das.

Von Agner Fog microarch Führung, S. 98 (Pentium M Abschnitt, durch den späteren Abschnitten einschließlich SnB verwiesen): auf Null

Der Prozessor erkennt die XOR eines Registers mit sich selbst als Einstellung. Eine spezielle Variable im Register erinnert daran, dass der obere Teil des Registers Null ist, so dass EAX = AL.Dieser Tag wird auch in einer Schleife erinnerte:

; Example 7.9. Partial register problem avoided in loop 
    xor eax, eax 
    mov ecx, 100 
LL: 
    mov al, [esi] 
    mov [edi], eax ; No extra uop 
    inc esi 
    add edi, 4 
    dec ecx 
    jnz LL 

(von pg82): Der Prozessor erinnert sich, dass die oberen 24 Bits von EAX sind Null, solange Sie keine Unterbrechung, Fehlvorhersage erhalten, oder andere Serialisierungsereignis.

pg82 diese Führung bestätigt auch, dass mov reg, 0nicht als Nullstellung Idiom, zumindest auf frühes P6 Design wie PIII oder PM anerkannt ist. Ich wäre sehr überrascht, wenn sie Transistoren bei späteren CPUs einsetzen würden.


xor Sets Fahnen, was bedeutet, Sie müssen vorsichtig sein, wenn die Bedingungen zu testen. Da setcc leider nur mit einem 8bit Ziel verfügbar ist, müssen Sie in der Regel darauf achten, Teilstrafen zu vermeiden.

Es wäre schön gewesen, wenn x86-64 einen der entfernten Opcodes (wie AAM) für ein 16/32/64 Bit setcc r/m mit dem im 3-Bit-Quellregister des r/m-Feld (die Art, wie einige andere Operanden-Anweisungen sie als Opcode-Bits verwenden). Aber das haben sie nicht gemacht, und das würde sowieso nicht für x86-32 helfen.

Idealerweise sollten Sie xor/set Fahnen verwenden/setcc/gelesenes volles Register:

... 
call some_func 
xor  ecx,ecx ; zero *before* the test 
test eax,eax 
setnz cl   ; cl = (some_func() != 0) 
add  ebx, ecx ; no partial-register penalty here 

Dies hat eine optimale Leistung auf allen CPUs (keine Stände, verschmelzenden Uops oder falsche Abhängigkeiten).

Die Dinge sind komplizierter, wenn Sie nicht vor einer Flag-Einstellung Anweisung xor wollen. z.B. Sie möchten auf eine Bedingung verzweigen und dann von denselben Flags aus auf eine andere Bedingung setzen. z.B. cmp/jle, sete, und Sie haben entweder kein Ersatzregister, oder Sie möchten den xor aus dem Pfad des nicht genommenen Codes zusammenhalten.

Es gibt keine erkannten Nullsetzungs-Idiome, die keine Flags beeinflussen, daher hängt die beste Wahl von der Zielmikroarchitektur ab. Bei Core2 kann das Einfügen eines zusammenführenden Ups zu einem 2- oder 3-stufigen Stillstand führen. Es scheint auf SnB billiger zu sein, aber ich habe nicht viel Zeit damit verbracht zu messen. Die Verwendung von mov reg, 0/setcc würde auf älteren Intel-CPUs eine erhebliche Strafe bedeuten und bei neueren Intel noch etwas schlechter sein.

setcc/movzx r32, r8 ist wahrscheinlich die beste Alternative für Intel P6 & SnB-Familien, wenn Sie nicht vor der Flag-Einstellung Anweisung Xor-Null sein können. Das sollte besser sein, als den Test nach einem xor-Nullsetzen zu wiederholen. (Denken Sie nicht einmal an sahf/lahf oder pushf/popf). IvB kann movzx r32, r8 eliminieren (d. H. Es kann mit Registerumbenennung ohne Ausführungseinheit oder Latenz, wie xor-Nullsetzung, umgehen). Haswell und später nur regelmäßige mov Anweisungen beseitigen, so movzx nimmt eine Ausführungseinheit und hat nicht Null Latenz, Test machen/setcc/movzx schlechter als xor/test/setcc, aber immer noch mindestens so gut wie Test/mov r,0/setcc (und viel besser auf älteren CPUs).

Die Verwendung von setcc/movzx ohne Nullsetzung ist bei AMD/P4/Silvermont schlecht, weil sie die Depos nicht separat nach Unterregistern verfolgen. Der alte Wert des Registers wäre falsch. Die Verwendung mov reg, 0/setcc zum Nullsetzen/Abhängigkeit brechen ist wahrscheinlich die beste Alternative, wenn xor/test/setcc keine Option ist.

Natürlich, wenn Sie nicht brauchen, setcc Ausgabe ist breiter als 8 Bits, müssen Sie 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 Vorsicht verursachen eine teilweise reg Stall oder zusätzliche UOP, wenn Sie eine Funktion aufrufen, die speichern kann/Wiederherstellen der registrieren Sie verwenden Teil.)


and mit einer sofortigen Null nicht speziell ist -kassiert als unabhängig von dem alten Wert auf irgendwelchen CPUs, die ich kenne, also bricht es nicht Abhängigkeitsketten. Es hat keine Vorteile gegenüber xor, und viele Nachteile.

Siehe http://agner.org/optimize/ für microarch Dokumentation, einschließlich der Nullstellung Idiome erkannt werden als Abhängigkeit zu brechen (zB sub same,same ist auf einige, aber nicht alle CPUs, während xor same,same auf all erkannt wird.) mov funktioniert die Abhängigkeitskette auf dem alten Wert des Bruch register (unabhängig vom Quellwert, null oder nicht, denn so funktioniert mov). xor bricht nur die Abhängigkeitsketten in dem speziellen Fall, in dem src und dest das gleiche Register sind, weshalb mov aus der Liste der speziell erkannten Abhängigkeitsbrecher weggelassen wird. (Auch, weil es nicht als Nullstellung Idiom erkannt wird, mit den anderen Vorteilen, die tragen.)

Interessanterweise ist das älteste P6-Design (PPro) tat nicht erkennen xor -zeroing als Abhängigkeit Brecher, nur als ein Nullungs-Idiom zum Zweck der Vermeidung von Partial-Register-Ständen, so dass es sich in einigen Fällen beide lohnt. (Siehe Agner Fogs Beispiel 6.17 in seinem microarch pdf. Er behauptet, dies gilt auch für P2, P3 und sogar (früh?) PM, aber ich bin skeptisch. A comment on the linked blog post sagt, es war nur PPro, die diese Aufsicht hatte wirklich unwahrscheinlich scheint, dass mehrere Generationen der Familie P6 existierten xor-Nullstellung als dep Brecher, ohne zu erkennen.)


Wenn es Ihren Code schöner oder speichert Anweisungen wirklich macht, dann sicher, Null mit mov zu vermeiden, die zu berühren Flags, solange Sie kein anderes Leistungsproblem als die Code-Größe einführen. Das Vermeiden von Clobber-Flags ist jedoch der einzige vernünftige Grund dafür, xor nicht zu verwenden.

+0

Interessant. Es ist also nicht wirklich 100% kostenlos. Ich meine, obwohl es keinen Port benutzt, kostet es immer noch eine Mikro-OP. Das ist eine Feinheit, die ich in Agners Handbuch vermisste. Vielen Dank! Es hat also keine Latenz, aber der Durchsatz ist 4 (oder 0,25 reziproker Durchsatz). –

+5

Die meisten arithmetischen Befehle OP R, S werden von einer nicht geordneten CPU gezwungen, auf den Inhalt des Registers R zu warten, um durch vorherige Befehle mit dem Register R als ein Ziel gefüllt zu werden; Dies ist eine Datenabhängigkeit.Der entscheidende Punkt ist, dass Intel/AMD-Chips über spezielle Hardware verfügen, um Abhängigkeiten von Register X zu unterbrechen, wenn XOR R, R auftritt, und dies nicht notwendigerweise für andere Register-Nullsetzungsbefehle. Dies bedeutet, dass die XOR-Anweisung zur sofortigen Ausführung geplant werden kann, und deshalb empfehlen Intel/AMD *, sie zu verwenden. –

+1

@IraBaxter: Yup, und nur um Verwirrung zu vermeiden (weil ich dieses Missverständnis auf SO gesehen habe), bricht 'mov reg, src' auch Dep-Ketten für OO-CPUs ab (unabhängig davon, ob src imm32,' [mem] ') oder ist ein anderes Register). Diese Abhängigkeitsunterbrechung 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 geschieht immer für Anweisungen, die nicht von ihrem Ziel abhängig sind. (abgesehen von Intels Implementierung von 'popcnt/lzcnt/tzcnt' mit einer falschen dep auf dem Ziel.) –

Verwandte Themen