RDTSCP versus RDTSC + CPUID

Lesezeit: 5 Minuten

Benutzer-Avatar
Ricky Mutschlechner

Ich mache einige Linux-Kernel-Timings, insbesondere im Interrupt-Handling-Pfad. Ich habe RDTSC für Timings verwendet, aber ich habe kürzlich erfahren, dass es nicht unbedingt genau ist, da die Anweisungen möglicherweise nicht in der richtigen Reihenfolge ausgeführt werden.

Ich habe dann versucht:

  1. RDTSC + CPUID (hier in umgekehrter Reihenfolge), um die Pipeline zu leeren, und bis zu 60x Overhead (!) auf einer virtuellen Maschine (meiner Arbeitsumgebung) aufgrund von Hypercalls und so weiter. Dies gilt sowohl mit als auch ohne aktivierte HW-Virtualisierung.

  2. Vor kurzem bin ich auf die Anweisung RDTSCP * gestoßen, die anscheinend das tut, was RDTSC + CPUID getan hat, aber effizienter, da es sich um eine neuere Anweisung handelt – nur ein relativer Overhead von 1,5x-2x.

Meine Frage ist RDTSCP wirklich genau als Messpunkt, und ist es die “richtige” Art, das Timing durchzuführen?

Um es klarer zu sagen, mein Timing ist intern im Wesentlichen so:

  • Speichern Sie den aktuellen Wert des Zykluszählers
  • Führen Sie eine Art von Benchmark durch (z. B. Festplatte, Netzwerk)
  • Addieren Sie das Delta des aktuellen und des vorherigen Zykluszählers zu einem Akkumulatorwert und inkrementieren Sie einen Zähler pro einzelnem Interrupt
  • Teilen Sie am Ende das Delta/den Akkumulator durch die Anzahl der Interrupts, um die durchschnittlichen Zykluskosten pro Interrupt zu erhalten.

*http://www.intel.de/content/dam/www/public/us/en/documents/white-papers/ia-32-ia-64-benchmark-code-execution-paper.pdf Seite 27

  • Nicht CPUID Implementieren Sie eine Art vollständige Speicherbarriere? Das wäre spürbar teuer.

    – Kerrek SB

    29. Dezember 2014 um 17:29 Uhr

  • @KerrekSB Ich denke, der Overhead kommt von den Hypercalls, einfach weil es fast keinen Overhead von CPUID gibt, wenn ich denselben Test in einer nicht virtualisierten Umgebung durchführe.

    – Ricky Mutschlechner

    29. Dezember 2014 um 17:30 Uhr

  • Ist es richtig? Nun, laut Whitepaper ist es nicht perfekt, aber es ist das Beste, was Sie haben. Beachten Sie, dass Out-of-Order-Effekte möglicherweise vernachlässigbar sind, wenn Ihr gemessener Abschnitt lang genug ist.

    – Leeor

    29. Dezember 2014 um 20:18 Uhr

  • CPUID löst bedingungslos einen VMExit aus, der bei der Virtualisierung einige tausend Zykluskosten verursachen sollte.

    – Ruthafjord

    28. Mai 2019 um 22:12 Uhr

  • Sind Sie sicher, dass “beide Anweisungen Ihnen einen monoton steigenden 64-Bit-Zähler liefern, der die Anzahl der Zyklen auf dem Prozessor darstellt”? Ich dachte, die Anweisungen seien ignorant für dynamisches Untertakten (z. B. Intels SpeedStep) und dynamisches Übertakten (z. B. Intels Turbo Boost) und geben daher nicht unbedingt die Anzahl der Zyklen an. Sie zählen mit der gleichen Rate, obwohl sich die Frequenz dynamisch ändert.

    – Z-Boson

    30. Dezember 2014 um 12:27 Uhr

  • @Zboson das war auch mein Verständnis.

    – Ricky Mutschlechner

    30. Dezember 2014 um 15:32 Uhr

  • Sie haben Recht, und das ist sowieso schlecht formuliert, da die Anweisungen Ihnen eine Momentaufnahme des Zählerwerts liefern, nicht den Zähler selbst.

    – dho

    30. Dezember 2014 um 16:30 Uhr

  • Außerdem mache ich es definitiv auf die zweite Art, nachdem ich das Whitepaper und so weiter gelesen habe. Danke für den Einblick!

    – Ricky Mutschlechner

    5. Januar 2015 um 1:16 Uhr

  • @Zboson Wenn sowohl rdtsc als auch rdtscp die dynamische Frequenzskalierung nicht kennen, wie hoch ist die Frequenz zum Erhöhen des Zählers? Gibt es bei vorhandener Frequenzskalierung trotzdem die Möglichkeit, die tatsächliche Zykluszahl zu erhalten?

    – Rakib

    12. Juni 2015 um 14:44 Uhr

  • Wenn Sie verwenden rdtsc / rdtscp zweimal müssen Sie das erste Ergebnis außerhalb von RDX:RAX speichern. zB statt orverwenden lea rcx, [rax + rdx] zusammenführen und das Ergebnis in ein anderes Register schreiben.

    – Peter Cordes

    28. September 2019 um 12:55 Uhr

  • @PeterCordes Der obige Code soll in zwei separate Funktionen gesteckt werden, die beispielsweise so verwendet werden: start = fenced_rdtsc(); to_be_benchmarked_code(); stop = fenced_rdtscp(); duration = stop-start;

    – maxschlepzig

    28. September 2019 um 13:05 Uhr

  • Oh. IDK, warum Sie Call / Ret-Overhead in Ihrer zeitgesteuerten Region benötigen, wenn Sie sich die Mühe machen, es zu verwenden rdtsc direkt für hohe Präzision, anstatt nur eine Wiederholungsschleife zu verwenden, um Messfehler auf eine vernachlässigbare Größe zu amortisieren. Aber ok. Ich hätte diese als CPP-Makros definiert, indem ich eine GNU C-Inline-asm-Anweisung verwendet und LEA verwendet hätte, damit das erste ein verwendet "=r" Ausgangsoperand. Oder da ich weiß, dass das Delta weit unter 2 ^ 32 liegen wird, ignorieren Sie einfach die obere Hälfte der Ausgabe und erklären Sie einen Clobber für on rdx und ein "=a" Ausgang. Hohe Eingangsbits haben keinen Einfluss auf die niedrigen 32 des Ausgangs für Sub.

    – Peter Cordes

    28. September 2019 um 22:27 Uhr


  • @PeterCordes Sie müssen kein CPP-Makro verwenden – natürlich “überzeugen” Sie den Compiler, diese Funktionen einzufügen, und dann haben Sie keinen Call/Ret-Overhead und es ist eine sicherere/besser wartbare Alternative zu einem CPP-Makro. (Sehen Sie auch, wie ich diesen Ansatz in einem meiner Projekte implementiert habe)

    – maxschlepzig

    29. September 2019 um 8:34 Uhr

  • Ich nahm an, Sie meinten, dass Ihre Funktionen in reinem Asm geschrieben wurden, nicht in Inline-Asm, wahrscheinlich weil Ihre Antwort mit fest codierten Registrierungen usw. geschrieben wurde. Wenn Sie das nicht tun, können Sie genauso gut Intrinsics verwenden _mm_lfence(), _mm_mfence()und _mm_rdtsc(). Wenn Sie Inline asm verwendet haben, wird der Compiler nicht widersprüchliche Register für die beiden auswählen wollen asm Aussagen. Oder fügen Sie ein Extra hinzu mov in den zeitgesteuerten Bereich, nachdem beide Anweisungen in ihren Aufrufer eingefügt wurden, wenn sie beide verwendet werden "=a" und Klobber RDX.

    – Peter Cordes

    29. September 2019 um 8:42 Uhr

  • Das sieht auch solide aus. Die Sache ist, dass ich tatsächlich die in CPU-Zyklen verstrichene Zeit benötige, da ich einen Mikrobenchmark für etwas durchführe, das in <100 Zyklen laufen soll, sobald wir mit der Optimierung fertig sind. Ich führe es auch in einer virtuellen Maschine aus, die (glaube ich) eine festgelegte Frequenz für den Prozessor hat. Ich werde dies noch einmal überprüfen und entsprechend bearbeiten.

    – Ricky Mutschlechner

    30. Dezember 2014 um 15:33 Uhr

  • @RickyMutschlechner, ja, durch das Timing vermutete ich, dass Sie möglicherweise Zyklen und nicht Zeit waren. Ich weiß nicht, wie man das auf einer virtuellen Maschine macht. Ich bezweifle sehr, dass Sie die dritte von mir vorgeschlagene Methode verwenden könnten. Ich bin sogar skeptisch gegenüber den anderen Methoden, weil Sie davon ausgehen müssen, dass die virtuelle Maschine das Hardware-Timing auf die gleiche Weise implementiert (gleiche Latenz und Durchsatz) und dies nicht einmal vollständig dokumentiert ist. Aber ich habe wenig Erfahrung mit virtuellen Maschinen. Mein Bauchgefühl sagt, nichts geht über das Testen auf echter Hardware. Dies auf einer virtuellen Maschine zu tun, ist eine interessante Frage.

    – Z-Boson

    2. Januar 2015 um 10:00 Uhr


1174880cookie-checkRDTSCP versus RDTSC + CPUID

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

Privacy policy