Kann MOV von x86 wirklich “kostenlos” sein? Warum kann ich das überhaupt nicht reproduzieren?

Lesezeit: 6 Minuten

Benutzeravatar von user541686
Benutzer541686

Ich sehe immer wieder Leute, die behaupten, dass die MOV-Anweisung in x86 wegen der Umbenennung von Registern kostenlos sein kann.

Ich kann das beim besten Willen nicht in einem einzigen Testfall überprüfen. Jeder Testfall, den ich versuche, entlarvt es.

Hier ist zum Beispiel der Code, den ich mit Visual C++ kompiliere:

#include <limits.h>
#include <stdio.h>
#include <time.h>

int main(void)
{
    unsigned int k, l, j;
    clock_t tstart = clock();
    for (k = 0, j = 0, l = 0; j < UINT_MAX; ++j)
    {
        ++k;
        k = j;     // <-- comment out this line to remove the MOV instruction
        l += j;
    }
    fprintf(stderr, "%d ms\n", (int)((clock() - tstart) * 1000 / CLOCKS_PER_SEC));
    fflush(stderr);
    return (int)(k + j + l);
}

Dies erzeugt den folgenden Assembler-Code für die Schleife (Sie können dies nach Belieben erstellen; Sie benötigen Visual C++ offensichtlich nicht):

LOOP:
    add edi,esi
    mov ebx,esi
    inc esi
    cmp esi,FFFFFFFFh
    jc  LOOP

Jetzt führe ich dieses Programm mehrmals aus und beobachte einen ziemlich konstanten Unterschied von 2%, wenn die MOV-Anweisung entfernt wird:

Without MOV      With MOV
  1303 ms         1358 ms
  1324 ms         1363 ms
  1310 ms         1345 ms
  1304 ms         1343 ms
  1309 ms         1334 ms
  1312 ms         1336 ms
  1320 ms         1311 ms
  1302 ms         1350 ms
  1319 ms         1339 ms
  1324 ms         1338 ms

Also was gibt? Warum ist MOV nicht “kostenlos”? Ist diese Schleife zu kompliziert für x86?
Gibt es eine Single Beispiel da draußen, das zeigen kann, dass MOV kostenlos ist, wie die Leute behaupten?
Wenn ja, was ist es? Und wenn nicht, warum behaupten immer alle, MOV sei kostenlos?

  • Bei der “Freiheit” geht es um Latenz, die Sie hier nicht messen. Auch 2% davon sind deutlich weniger als ein Zyklus, also nur aufgrund von “seltsamen Effekten”.

    – Harald

    24. Mai 2017 um 22:19 Uhr

  • Nun, was bedeutet “vollständig entfernt” überhaupt. Natürlich kann es vor dem Decodieren nicht entfernt werden, da noch nicht einmal bekannt ist, was es ist. Es überrascht nicht, dass der Umbenennungstrick den Mov bestenfalls während des Umbenennens entfernen kann und dann nicht einmal immer. Nur weil sie da ist, kann die Bewegung nicht sein völlig frei.

    – Harald

    24. Mai 2017 um 22:35 Uhr

  • Sie haben 25 % mehr Anweisungen hinzugefügt, aber es ist nur 2 % langsamer. Sie können das nicht mit “scheint es gibt keine MOV-Eliminierung” erklären. Ein Unterschied von 2 % erfordert eine andere Erklärung, wie z. B. dass der Kern zu heiß wird und zurück gedrosselt wird.

    – Hans Passant

    24. Mai 2017 um 22:59 Uhr

  • Durch die Registerumbenennung wird der MOV effektiv aus dem Back-End entfernt, was bedeutet, dass er aus 0 µops besteht, keinen Ausführungsport verbraucht und eine Latenz von 0 hat. Die Anweisung selbst muss jedoch noch entschlüsselt werden, was nicht kostenlos ist. Außerdem nimmt es Platz im Code ein, was Platz im Cache bedeutet. Also nein, ein MOV ist nie wirklich kostenlos, weil es im Frontend Kosten gibt, aber es ist oft so effektiv frei im Kontext eines größeren Codeblocks, der eine sinnvolle Operation ausführt. Ein Unterschied von 2% in der Ausführungsgeschwindigkeit ist deutlich weit weniger als ein Zyklus, wie man naiv erwarten würde.

    – Cody Grey

    26. Mai 2017 um 9:44 Uhr


  • @CodyGray: Ein eliminiertes MOV nimmt Platz im ROB ein, bis es auf Intel-Hardware zurückgezogen wird (wie eine xor-Null-Anweisung oder sogar ein NOP) (ohne Fehlvorhersagen für Zweige, uops_retired.retire_slots wird fast genau passen uops_issued.any). Mein mentales Modell ist, dass sie in den ROB (Fused-Domain) in einem bereits ausgeführten Zustand eintreten, in dem sie sich zurückziehen können, ohne dass Uops der unfusionierten Domain in den RS (Scheduler) ausgegeben werden. Vermutlich gibt es etwas nicht Triviales daran, keine uop zu haben, um sich für eine Anweisung zurückzuziehen, vielleicht etwas über die Aktualisierung von RIP oder einfach über das Zurücksetzen von Fehlspekulationen …

    – Peter Cordes

    28. Mai 2017 um 3:59 Uhr


  • +1 tolle Antwort! Einiges davon ging tatsächlich über meinen Kopf (z. B. hatte ich noch nie von “fusioned-domain” gehört), aber ich glaube, ich habe verstanden, was los ist. Vielen Dank!

    – Benutzer541686

    26. Mai 2017 um 10:18 Uhr

  • Ja, ich bin mir ziemlich sicher, dass ich es verstehe. Sie sagen, dec + jnz werden zu 1 Operation verschmolzen, und wenn das mov eliminiert wird, laufen 2 Operationen jeweils für 4 Anweisungen, und jede dauert einen Zyklus, was 2,00 ins/Zyklus ergibt, und analog zu 1,33 und 1,50 Fälle. Die 2% sind definitiv neugierig, da stimme ich zu. Aber es ist eine wirklich gute Antwort; Irgendwann wollte ich es akzeptieren, hatte es nur nicht eilig. Danke, dass du es geschrieben hast.

    – Benutzer541686

    26. Mai 2017 um 23:39 Uhr

  • @JDługosz: movzx eax, bl ist 8 bis 64. Der Teil 32 -> 64 ergibt sich implizit aus dem Schreiben eines 32-Bit-Registers (stackoverflow.com/questions/11177137/…). Schreiben movzx rax, bl würde den Code ohne Nutzen vergrößern (REX-Präfix).

    – Peter Cordes

    19. September 2017 um 7:34 Uhr


  • @BeeOnRope: Oh, FFS Intel, teste deine CPUs besser, damit wir nicht weiter an Leistungseinbußen herumarbeiten müssen, die durch Abschwächungen verursacht wurden. Zumal Intels Optimierungsratschlag für IvyBridge darin bestand, das Ergebnis von a bevorzugt zu überschreiben mov sofort, um Mov-Elimination-Ressourcen freizugeben, was es wahrscheinlicher für die macht mov ohne Eliminierung auf dem kritischen Pfad sein. (Und Compiler scheinen es vorzuziehen, mehr mit der Kopie statt mit dem Original zu tun, nachdem sie eine Kopie erstellt haben.)

    – Peter Cordes

    10. März 2021 um 23:18 Uhr

  • @Noah: Schade, dass Intel Microcode nicht Open Source ist; Wir wissen, dass das LSD per Mikrocode deaktiviert werden kann, wie in der Skylake-Familie. (Wenn Sie mehrere Computer zur Auswahl hätten, könnten Sie natürlich einfach einen SKL verwenden, dessen LSD durch Mikrocode deaktiviert ist, im Vergleich zu einem, der dies nicht tat, wobei Sie davon ausgehen, dass sie ansonsten mikroarchitektonisch identisch sind.)

    – Peter Cordes

    28. April 2021 um 0:57 Uhr

  • @Mehrdad weil die movs befinden sich jetzt in der Abhängigkeitskette, wenn sie also eine Latenz hätten, müsste sich das summieren. In Ihrem Testfall ist die mov baumelt einfach am Ende der Kette, nichts wartet darauf, dass es passiert. Es könnte eliminiert werden oder nicht, es gibt keine Möglichkeit zu sagen.

    – Harald

    24. Mai 2017 um 23:00 Uhr


  • @Mehrdad die Zeiten sind unterschiedlich, ja. Aber Latenz kann immer nur eine ganze Zahl von Zyklen sein (inb4 Netburst mit seiner seltsamen dual-gepumpten ALU), also mov fügt entweder einen Zyklus hinzu oder nicht (in diesem Fall muss er eliminiert worden sein). Dass seine bloße Anwesenheit hat Sonstiges (subtilere) Effekte, ist wirklich unabhängig. Sie haben natürlich völlig recht, dass es diese Effekte gibt.

    – Harald

    24. Mai 2017 um 23:11 Uhr


  • @Mehrdad, das kommt ein bisschen in seltsame Fälle, da es davon abhängt, wie es implementiert wird, zumindest ist es möglich Versuchen um es zu messen, da es fiktiv etwas liest und etwas schreibt. Wenn Sie dies tatsächlich tun (z. B. durch Anpassen des Codes aus meinem zweiten Testfall), wird die Latenz auf Haswell als 1 angezeigt (dh sie wird nicht eliminiert). Mir fällt auf Anhieb kein Grund dafür ein, aber so ist es

    – Harald

    24. Mai 2017 um 23:39 Uhr

  • @Mehrdad oh sorry ja, eine durchschnittliche Latenz kann eine Nicht-Ganzzahl sein. Unter der Hypothese, dass das, was passiert, ist gelegentlich Wenn Sie den Mov nicht eliminieren, könnten Sie sogar sagen, dass die Latenz im Durchschnitt eine niedrige, aber von Null verschiedene Zahl ist. AFAIK liegt es nur an anderen Effekten, aber einen Versuch ist es allemal wert. E: Wenn sich beispielsweise die konsistente kleine Strafe für mein zweites Beispiel erheblich ändert, wenn “anderer harmloser Müll” anstelle von movs eingefügt wird, könnte dies auf etwas Interessantes in dieser Richtung hindeuten.

    – Harald

    24. Mai 2017 um 23:50 Uhr


  • Läuft das Baremetal? mit oder ohne aktiviertem Cache? Sie passen die Abrufausrichtung um mindestens 16, wenn nicht 32 Bytes an?

    – Oldtimer

    25. Mai 2017 um 2:55 Uhr

1399140cookie-checkKann MOV von x86 wirklich “kostenlos” sein? Warum kann ich das überhaupt nicht reproduzieren?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy