2009-07-16 6 views
39

Es gibt zwei bekannte Möglichkeiten, ein Ganzzahlregister auf x86 auf null zu setzen.Macht die Verwendung von xor reg, reg einen Vorteil gegenüber mov reg, 0?

Entweder

mov reg, 0 

oder

xor reg, reg 

Es gibt eine Meinung, dass die zweite Variante besser ist, da der Wert 0 im Code nicht gespeichert ist und speichert mehrere Bytes von Maschinencode erzeugt. Dies ist definitiv gut - es wird weniger Befehlscache verwendet und dies kann manchmal eine schnellere Codeausführung ermöglichen. Viele Compiler produzieren solchen Code.

Es gibt jedoch formal eine Abhängigkeit zwischen Anweisungen zwischen der xor-Anweisung und einer früheren Anweisung, die dasselbe Register ändert. Da es eine Abhängigkeit gibt, muss die letztere Anweisung warten, bis die erstere abgeschlossen ist, und dies könnte die Prozessoreinheitslast und die Verletzungsleistung reduzieren.

add reg, 17 
;do something else with reg here 
xor reg, reg 

Es ist offensichtlich, dass das Ergebnis von XOR unabhängig vom anfänglichen Registerwert genau gleich ist. Aber kann der Prozessor dies erkennen?

Ich habe versucht, den folgenden Test in VC++ 7:

const int Count = 10 * 1000 * 1000 * 1000; 
int _tmain(int argc, _TCHAR* argv[]) 
{ 
    int i; 
    DWORD start = GetTickCount(); 
    for(i = 0; i < Count ; i++) { 
     __asm { 
      mov eax, 10 
      xor eax, eax 
     }; 
    } 
    DWORD diff = GetTickCount() - start; 
    start = GetTickCount(); 
    for(i = 0; i < Count ; i++) { 
     __asm { 
      mov eax, 10 
      mov eax, 0 
     }; 
    } 
    diff = GetTickCount() - start; 
    return 0; 
} 

Mit Optimierungen aus beiden Schleifen nehmen genau die gleiche Zeit. Beweist dies vernünftigerweise, dass der Prozessor erkennt, dass es keine Abhängigkeit von xor reg, reg Anweisung auf der früheren mov eax, 0 Anweisung gibt? Was könnte ein besserer Test sein, um dies zu überprüfen?

+2

Ich denke, deshalb verwenden wir Hochsprachen. Wenn Sie wirklich wissen wollen, ändern Sie einfach die Codegen-Stufe, um das eine oder andere zu tun. Benchmark. Wähle das Beste aus. – jrockway

+3

ah, die alte 'xor reg, reg 'Trick - gute alte Zeiten :) –

+1

Ich denke, die x86-Architektur definiert explizit XOR reg, reg als die Abhängigkeit von reg. Siehe das Intel Architekturhandbuch. Ich würde MOV reg erwarten, ... das Gleiche zu tun, einfach weil es ein MOV ist. Deine wirkliche Wahl ist also, welche man weniger Platz benötigt (ich würde raten, dass die Ausführungszeit gleich ist), wenn dir Statusbits egal sind (XOR schädigt sie alle). –

Antwort

25

eine tatsächliche Antwort für Sie:

Intel 64 and IA-32 Architectures Optimization Reference Manual

Abschnitt 3.5.1.8 ist, wo Sie suchen möchten.

Kurz gesagt gibt es Situationen, in denen ein xor oder ein mov bevorzugt werden kann. Die Probleme konzentrieren sich auf Abhängigkeitsketten und die Erhaltung von Bedingungscodes.

+0

Es klingt nicht so, als ob der zitierte Text empfiehlt, einen MOV in irgendeiner Situation zu verwenden. – mwfearnley

+0

@mwfearnley Leider hat sich Addison entschieden, meine Antwort zu bearbeiten und eine Auswahl des Inhalts auszuwählen, es ist unklar, warum das gemacht wurde. Sie sollten die vollständigen Dokumente lesen, die Situationen abdecken, in denen mov bevorzugt wird. – Mark

+0

Danke für die Klärung. Ich denke, es war ein Versuch, das Problem mit dem Verschieben/Ändern des Dokuments zu vermeiden, aber leider enthielt das Zitat nicht alle Punkte, die es benötigt .. Ich kann jetzt aus diesem Abschnitt sehen, MOV zu verwenden, wenn Sie vermeiden möchten Einstellen der Bedingungscodes. – mwfearnley

2

Ich denke auf früheren Architekturen die mov eax, 0 Anweisung verwendet, um ein wenig länger als die xor eax, eax als auch ... kann nicht genau, warum zu erinnern. Es sei denn, Sie haben viel mehr mov s, aber ich würde mir vorstellen, dass Sie aufgrund des einen im Code gespeicherten Literals wahrscheinlich keine Cache-Fehler verursachen.

Beachten Sie auch, dass aus dem Speicher der Status der Flags nicht identisch zwischen diesen Methoden ist, aber ich kann dies falsch zu erinnern.

12

Ich hörte auf, meine eigenen Autos reparieren zu lassen, nachdem ich meinen HR-Kombi 1966 verkaufte. Ich bin in einer ähnlichen Lösung mit modernen CPUs :-)

Es wird wirklich auf die zugrunde liegenden Mikrocode oder Schaltungen abhängen. Es ist durchaus möglich, dass die CPU "XOR Rn,Rn" erkennt und einfach alle Bits zerlegt, ohne sich um den Inhalt zu kümmern. Aber natürlich kann es dasselbe mit einer "MOV Rn, 0" machen. Ein guter Compiler wählt sowieso die beste Variante für die Zielplattform, daher ist dies normalerweise nur ein Problem, wenn Sie in Assembler programmieren.

Wenn die CPU intelligent genug ist, Ihre XOR Abhängigkeit verschwindet, da es weiß der Wert irrelevant ist und setzt sie auf jeden Fall auf Null (wieder das hängt von der tatsächlichen CPU verwendet wird).

Allerdings bin ich lange vorbei kümmern für ein paar Bytes oder ein paar Taktzyklen in meinem Code - das scheint wie Mikro-Optimierung verrückt geworden.

+3

Unabhängig davon, ob es sich um eine übermäßige Optimierung für den praktischen Gebrauch handelt, kann es sinnvoll sein zu verstehen, dass nicht alle ähnlichen Anweisungen gleich sind. ;) – jerryjvl

+3

@jerryjvl - Es ist auch nützlich zu erkennen, dass moderne x86-Desktop-CPUs nicht x86-Maschinencode ausführen - sie dekodieren die x86 in eine RISC wie interne Anweisungen auszuführen. Als solche können sie gemeinsame Codefolgen (wie xor eax, eax) erkennen und sie in einfachere Anweisungen übersetzen, wie zum Beispiel eine Anweisung "clear reg". Ein tatsächlicher Xor wird in diesem Fall wahrscheinlich nicht gemacht. – Michael

+0

Mikro-Optimierung muss möglicherweise verrückt werden, wenn Sie einen MBR schreiben =). – brianmearns

-8

Wie andere bemerkt haben, lautet die Antwort: "Wen kümmert es?". Schreibst du einen Compiler?

Und auf eine zweite Anmerkung, wird Ihr Benchmarking wahrscheinlich nicht funktionieren, da Sie eine Verzweigung dort haben, die wahrscheinlich sowieso die ganze Zeit dauert. (es sei denn, Ihr Compiler entrollt die Schleife für Sie)

Ein anderer Grund, dass Sie einen einzelnen Befehl in einer Schleife nicht benchmarken können, ist, dass Ihr gesamter Code zwischengespeichert wird (anders als echter Code). Sie haben also einen großen Teil des Größenunterschieds zwischen mov eax, 0 und xor eax aus dem Bild genommen, indem Sie sie die ganze Zeit im L1-Cache gespeichert haben.

Meine Vermutung ist, dass jeder messbare Leistungsunterschied in der realen Welt aufgrund des Größenunterschieds, der den Cache verschlingt, und nicht aufgrund der Ausführungszeit der beiden Optionen, wäre.

+9

Diese gesamte Website hat eine "who cares" Qualität für den Rest der Welt. Ich denke nicht, dass das eine gute Antwort wäre. –

9

x86 hat Befehle variabler Länge. MOV EAX, 0 benötigt ein oder zwei weitere Bytes im Coderaum als XOR EAX, EAX.

+5

'mov eax, 0' ist 5 Bytes: eins für den Operationscode" mov eax, imm32 "und 4 für die 4B der unmittelbaren Daten. 'xor eax, eax' ist 2 Bytes: ein' xor r32, r/m32' Opcode, einer für Operanden. –

6

Bei modernen CPUs wird das XOR-Muster bevorzugt. Es ist kleiner und schneller.

Kleiner ist eigentlich wichtig, weil bei vielen realen Arbeitslasten einer der Hauptfaktoren, die die Leistung begrenzen, i-Cache-Fehler sind. Dies würde nicht in einem Mikro-Benchmark verglichen werden, der die beiden Optionen vergleicht, aber in der realen Welt wird Code etwas schneller laufen.

Und ignoriert die reduzierten i-Cache-Fehler, XOR auf jeder CPU in den letzten Jahren ist die gleiche Geschwindigkeit oder schneller als MOV. Was könnte schneller sein als eine MOV-Anweisung auszuführen? Keine Ausführung von Anweisungen! Bei neueren Intel-Prozessoren erkennt die Verteilungs-/Umbenennungslogik das XOR-Muster, "realisiert", dass das Ergebnis Null ist, und zeigt das Register nur auf ein physikalisches Null-Register. Es verwirft dann die Anweisung, da es nicht notwendig ist, sie auszuführen.

Das Endergebnis ist, dass das XOR-Muster null Ausführungsressourcen verwendet und bei aktuellen Intel-CPUs vier Befehle pro Zyklus ausführen kann. MOV ist bei drei Befehlen pro Zyklus höher.

Einzelheiten finden Sie in diesem Blog-Post, die ich schrieb:

https://randomascii.wordpress.com/2012/12/29/the-surprising-subtleties-of-zeroing-a-register/

Die meisten Programmierer sollten nicht darüber Sorgen, aber Compiler Schriftsteller keine Sorge haben, und es ist gut, den Code zu verstehen, ist erzeugt werden, und es ist nur verdammt cool!