2016-04-24 23 views
1

Ich mache gerade eine Zuweisung, die die Leistung verschiedener x86-64-Befehle misst (bei & t-Syntax).x86-64 Relative JMP-Leistung

Der Befehl, den ich etwas verwirrt bin, ist der Befehl "unconditional jmp". Dies ist, wie ich es implementiert habe:

.global uncond 
uncond: 

.rept 10000 
jmp . + 2 
.endr 


mov $10000, %rax 
ret 

Es ist ziemlich einfach. Der Code erstellt eine Funktion namens "Uncond", die die Direktive .rept verwendet, um den jmp-Befehl 10000 Mal aufzurufen, und setzt den Rückgabewert auf die Häufigkeit, mit der Sie den Befehl jmp aufgerufen haben.

"." in & t Syntax bedeutet die aktuelle Adresse, die ich um 2 Bytes zu erhöhen, um die JMP-Anweisung selbst zu berücksichtigen (so Jmp. + 2 sollte einfach auf die nächste Anweisung verschieben).

Code, den ich nicht gezeigt habe, berechne die Anzahl der Zyklen, die benötigt werden, um die 10000 Befehle zu verarbeiten.

Meine Ergebnisse sagen JMP ist ziemlich langsam (dauert 10 Zyklen, um eine einzige JMP-Anweisung zu verarbeiten) - aber was ich über Pipelining verstehe, sollten unbedingte Sprünge sehr schnell sein (keine Verzweigung Vorhersagefehler).

Fehle ich etwas? Ist mein Code falsch?

+0

Mögliches Duplikat von [Slow jmp-instruction] (http://stackoverflow.com/questions/38811901/slow-jmp-instruction).Diese ausführlichere Frage hat eine viel bessere und detailliertere Antwort. –

Antwort

1

Die CPU ist nicht jmp Anweisungen für No-op optimiert, so dass es nicht den speziellen Fall weiterhin behandelt JMP Befehle zu dekodieren und Pipeline, die nur auf die nächste insn springen.

CPUs sind jedoch für Loops optimiert. jmp . läuft bei einigen CPUs mit einem Takt pro Takt oder bei einigen CPUs mit einem Takt pro Takt.


Ein Sprung erzeugt beim Abholen von Anweisungen eine Blase. Ein einzelner gut vorhergesagter Sprung ist in Ordnung, aber das Laufen von nichts als Sprüngen ist problematisch. Ich reproduziert Ihre Ergebnisse auf einem Core2 E6600 (Merom/Conroe microarch):

# jmp-test.S 
.globl _start 
_start: 

    mov $100000, %ecx 
jmp_test: 
    .rept 10000 
    jmp . + 2 
    .endr 

    dec %ecx 
    jg jmp_test 


    mov $231, %eax 
    xor %ebx,%ebx 
    syscall   # exit_group(0) 

bauen und laufen mit:

gcc -static -nostartfiles jmp-test.S 
perf stat -e task-clock,cycles,instructions,branches,branch-misses ./a.out 

Performance counter stats for './a.out': 

     3318.616490  task-clock (msec)   # 0.997 CPUs utilized   
    7,940,389,811  cycles     # 2.393 GHz      (49.94%) 
    1,012,387,163  instructions    # 0.13 insns per cycle   (74.95%) 
    1,001,156,075  branches     # 301.679 M/sec     (75.06%) 
      151,609  branch-misses    # 0.02% of all branches   (75.08%) 

     3.329916991 seconds time elapsed 

Aus einer anderen Sicht:

7,886,461,952  L1-icache-loads   # 2377.687 M/sec     (74.95%) 
    7,715,854  L1-icache-load-misses  # 2.326 M/sec     (50.08%) 
1,012,038,376  iTLB-loads    # 305.119 M/sec     (75.06%) 
      240  iTLB-load-misses   # 0.00% of all iTLB cache hits (75.02%) 

(Zahlen in Klammer (%) Am Ende jeder Zeile steht, wie viel von der gesamten Laufzeit, für die der Zähler aktiv war: perf muss für Sie multiplexen, wenn Sie darum bitten, mehr Dinge zu zählen, als die HW sofort zählen kann).

Also es ist nicht wirklich I-Cache-Misses, es ist nur Befehl holen/decodieren Front-End Engpässe durch ständige Sprünge verursacht.

Mein SnB-Rechner ist defekt, daher kann ich keine Zahlen darauf testen, aber 8 Zyklen pro JMP-Dauerdurchsatz liegen ziemlich nahe an Ihren Ergebnissen (die wahrscheinlich von einer anderen Mikroarchitektur stammen).

Weitere Informationen finden Sie unter http://agner.org/optimize/ und andere Links aus dem Tag Wiki.