Wird bei gettimeofday () eine Auflösung von Mikrosekunden garantiert?


97

Ich portiere ein Spiel, das ursprünglich für die Win32-API geschrieben wurde, nach Linux (nun, ich portiere den OS X-Port des Win32-Ports nach Linux).

Ich habe implementiert, QueryPerformanceCounterindem ich die uSeconds seit dem Start des Prozesses angegeben habe:

BOOL QueryPerformanceCounter(LARGE_INTEGER* performanceCount)
{
    gettimeofday(&currentTimeVal, NULL);
    performanceCount->QuadPart = (currentTimeVal.tv_sec - startTimeVal.tv_sec);
    performanceCount->QuadPart *= (1000 * 1000);
    performanceCount->QuadPart += (currentTimeVal.tv_usec - startTimeVal.tv_usec);

    return true;
}

Dies, zusammen mit der QueryPerformanceFrequency()Angabe einer konstanten Frequenz von 1000000 als Frequenz, funktioniert auf meinem Computer gut und gibt mir eine 64-Bit-Variable, die uSecondsseit dem Start des Programms enthält .

Ist das also tragbar? Ich möchte nicht herausfinden, dass es anders funktioniert, wenn der Kernel auf eine bestimmte Weise kompiliert wurde oder so etwas. Ich bin damit einverstanden, dass es nicht auf etwas anderes als Linux portierbar ist.

Antworten:


57

Vielleicht. Aber du hast größere Probleme. gettimeofday()kann zu falschen Timings führen, wenn auf Ihrem System Prozesse vorhanden sind, die den Timer ändern (z. B. ntpd). Unter einem "normalen" Linux glaube ich jedoch, dass die Auflösung von gettimeofday()10us beträgt. Es kann vorwärts und rückwärts und folglich zeitlich springen, basierend auf den Prozessen, die auf Ihrem System ausgeführt werden. Dies macht effektiv die Antwort auf Ihre Frage nein.

Sie sollten nach clock_gettime(CLOCK_MONOTONIC)Zeitintervallen suchen . Es leidet unter mehreren weniger Problemen aufgrund von Dingen wie Mehrkernsystemen und externen Uhreinstellungen.

Schauen Sie sich auch die clock_getres()Funktion an.


1
clock_gettime ist nur unter dem neuesten Linux verfügbar. andere Systeme haben nur gettimeofday ()
vitaly.v.ch

3
@ vitaly.v.ch es ist POSIX, also nicht nur Linux und "Newist"? Sogar 'Enterprise'-Distributionen wie Red Hat Enterprise Linux basieren auf 2.6.18, das clock_gettime hat, also nein, nicht sehr neu Sprechen Sie über WIRKLICH FREAKING ALTE Kernel WTF meinen Sie?
Spudd86

clock_gettime wurde 2001 in POSIX aufgenommen. Soweit ich weiß, ist clock_gettime () derzeit in Linux 2.6 und qnx implementiert. Derzeit wird Linux 2.4 jedoch in vielen Produktionssystemen verwendet.
vitaly.v.ch

Es wurde im Jahr 2001 eingeführt, aber nicht obligatorisch bis POSIX 2008.
R .. GitHub STOP HELPING ICE

2
Aus den Linux-FAQ für lock_gettime (siehe Antwort von David Schlosnagle) "CLOCK_MONOTONIC ... wird von NTP über adjtimex () frequenzangepasst. In Zukunft (ich versuche immer noch, den Patch zu installieren) wird es einen CLOCK_MONOTONIC_RAW geben, der dies nicht tut." überhaupt modifiziert werden und eine lineare Korrelation mit den Hardware-Zählern haben. " Ich glaube nicht, dass die _RAW-Uhr jemals in den Kernel gelangt ist (es sei denn, sie wurde in _HR umbenannt, aber meine Untersuchungen legen nahe, dass die Bemühungen ebenfalls aufgegeben werden).
Tony Delroy

41

Hohe Auflösung, geringes Overhead-Timing für Intel-Prozessoren

Wenn Sie mit Intel-Hardware arbeiten, lesen Sie hier den Echtzeit-Befehlszähler der CPU. Hier erfahren Sie, wie viele CPU-Zyklen seit dem Start des Prozessors ausgeführt wurden. Dies ist wahrscheinlich der feinkörnigste Zähler, den Sie zur Leistungsmessung erhalten können.

Beachten Sie, dass dies die Anzahl der CPU-Zyklen ist. Unter Linux können Sie die CPU-Geschwindigkeit von / proc / cpuinfo abrufen und teilen, um die Anzahl der Sekunden zu ermitteln. Dies in ein Doppel umzuwandeln ist sehr praktisch.

Wenn ich das auf meiner Box laufen lasse, bekomme ich

11867927879484732
11867927879692217
it took this long to call printf: 207485

Hier ist das Intel-Entwicklerhandbuch, das jede Menge Details enthält.

#include <stdio.h>
#include <stdint.h>

inline uint64_t rdtsc() {
    uint32_t lo, hi;
    __asm__ __volatile__ (
      "xorl %%eax, %%eax\n"
      "cpuid\n"
      "rdtsc\n"
      : "=a" (lo), "=d" (hi)
      :
      : "%ebx", "%ecx");
    return (uint64_t)hi << 32 | lo;
}

main()
{
    unsigned long long x;
    unsigned long long y;
    x = rdtsc();
    printf("%lld\n",x);
    y = rdtsc();
    printf("%lld\n",y);
    printf("it took this long to call printf: %lld\n",y-x);
}

11
Beachten Sie, dass der TSC möglicherweise nicht immer zwischen Kernen synchronisiert ist, seine Frequenz stoppt oder ändert, wenn der Prozessor in den Energiesparmodus wechselt (und Sie keine Möglichkeit haben, dies zu wissen), und im Allgemeinen nicht immer zuverlässig ist. Der Kernel kann erkennen, wann er zuverlässig ist, andere Alternativen wie HPET und ACPI PM-Timer erkennen und automatisch die beste auswählen. Es ist eine gute Idee, den Kernel immer für das Timing zu verwenden, es sei denn, Sie sind sich wirklich sicher, dass der TSC stabil und monoton ist.
CesarB

12
Die TSC auf Core- und höheren Intel-Plattformen wird über mehrere CPUs hinweg synchronisiert und unabhängig von den Energieverwaltungszuständen mit einer konstanten Frequenz erhöht. Siehe Intel Software Developer's Manual, Vol. 3, No. 3 Abschnitt 18.10. Die Rate, mit der der Zähler inkrementiert, entspricht jedoch nicht der Frequenz der CPU. Die TSC erhöht sich mit „der maximal aufgelösten Frequenz der Plattform, die dem Produkt aus skalierbarer Busfrequenz und maximal aufgelöstem Busverhältnis entspricht“. Intel Software Developer's Manual, Vol. 3, No. 3 Abschnitt 18.18.5. Sie erhalten diese Werte aus den modellspezifischen Registern (MSRs) der CPU.
Sstock

7
Sie können die skalierbare Busfrequenz und das maximal aufgelöste Busverhältnis erhalten, indem Sie die modellspezifischen Register (MSRs) der CPU wie folgt abfragen: Skalierbare Busfrequenz == MSR_FSB_FREQ [2: 0] id 0xCD, maximal aufgelöstes Busverhältnis == MSR_PLATFORM_ID [12: 8] id 0x17. Informationen zur Interpretation der Registerwerte finden Sie in Intel SDM Vol.3, Anhang B.1. Sie können die msr-tools unter Linux verwenden, um die Register abzufragen. kernel.org/pub/linux/utils/cpu/msr-tools
sstock

1
Sollte Ihr Code CPUIDnach der ersten RDTSCAnweisung und vor der Ausführung des zu vergleichenden Codes nicht erneut verwendet werden? Was kann sonst verhindern, dass der Benchmark-Code vor / parallel zum ersten ausgeführt RDTSCund folglich im RDTSCDelta unterrepräsentiert wird ?
Tony Delroy

18

@Bernard:

Ich muss zugeben, der größte Teil Ihres Beispiels ging mir direkt über den Kopf. Es kompiliert und scheint jedoch zu funktionieren. Ist dies sicher für SMP-Systeme oder SpeedStep?

Das ist eine gute Frage ... Ich denke, der Code ist in Ordnung. Aus praktischer Sicht verwenden wir es jeden Tag in meiner Firma und laufen auf einer ziemlich großen Auswahl an Boxen, alles von 2-8 Kernen. Natürlich YMMV usw., aber es scheint eine zuverlässige und kostengünstige Timing-Methode zu sein (da dadurch kein Kontext in den Systemraum gewechselt wird).

Im Allgemeinen funktioniert es wie folgt:

  • Deklarieren Sie den Codeblock als Assembler (und flüchtig, damit der Optimierer ihn in Ruhe lässt).
  • Führen Sie den CPUID-Befehl aus. Zusätzlich zum Abrufen einiger CPU-Informationen (mit denen wir nichts tun) wird der Ausführungspuffer der CPU synchronisiert, sodass die Timings nicht durch eine Ausführung außerhalb der Reihenfolge beeinflusst werden.
  • Führen Sie die Ausführung von rdtsc (read timestamp) aus. Dies ruft die Anzahl der Maschinenzyklen ab, die seit dem Zurücksetzen des Prozessors ausgeführt wurden. Dies ist ein 64-Bit-Wert, der bei den aktuellen CPU-Geschwindigkeiten etwa alle 194 Jahre auftritt. Interessanterweise stellen sie in der ursprünglichen Pentium-Referenz fest, dass sie sich etwa alle 5800 Jahre dreht.
  • In den letzten Zeilen werden die Werte aus den Registern in den Variablen hi und lo gespeichert und in den 64-Bit-Rückgabewert eingefügt.

Besondere Hinweise:

  • Eine Ausführung außerhalb der Reihenfolge kann zu falschen Ergebnissen führen. Daher führen wir die Anweisung "cpuid" aus, die nicht nur einige Informationen zur CPU liefert, sondern auch die Ausführung einer Anweisung außerhalb der Reihenfolge synchronisiert.

  • Die meisten Betriebssysteme synchronisieren die Zähler auf den CPUs, wenn sie gestartet werden, sodass die Antwort innerhalb weniger Nanosekunden gut ist.

  • Der Kommentar zum Ruhezustand ist wahrscheinlich wahr, aber in der Praxis interessieren Sie sich wahrscheinlich nicht für das Timing über die Grenzen des Ruhezustands hinweg.

  • in Bezug auf Speedstep: Neuere Intel-CPUs kompensieren die Geschwindigkeitsänderungen und geben eine angepasste Anzahl zurück. Ich habe einige der Boxen in unserem Netzwerk schnell gescannt und nur eine Box gefunden, die sie nicht hatte: einen Pentium 3 mit einem alten Datenbankserver. (Dies sind Linux-Boxen, also habe ich Folgendes überprüft: grep Konstante_tsc / proc / cpuinfo)

  • Ich bin mir bei den AMD-CPUs nicht sicher, wir sind in erster Linie ein Intel-Shop, obwohl ich weiß, dass einige unserer Low-Level-Systemgurus eine AMD-Evaluierung durchgeführt haben.

Ich hoffe, dies befriedigt Ihre Neugier, es ist ein interessanter und (IMHO) wenig erforschter Bereich der Programmierung. Weißt du, als Jeff und Joel darüber sprachen, ob ein Programmierer C kennen sollte oder nicht? Ich schrie sie an: "Hey, vergiss das hochrangige C-Zeug ... Assembler ist das, was du lernen solltest, wenn du wissen willst, was der Computer tut!"


1
... Die Kernel-Leute haben versucht, die Leute dazu zu bringen, rdtsc für eine Weile nicht mehr zu verwenden ... und generell zu vermeiden, es im Kernel zu verwenden, weil es einfach so unzuverlässig ist.
Spudd86

1
Als Referenz lautete die Frage, die ich gestellt habe (in einer separaten Antwort - vor Kommentaren): "Ich muss zugeben, der größte Teil Ihres Beispiels ist mir direkt über den Kopf gegangen. Es wird kompiliert und scheint jedoch zu funktionieren. Ist dies sicher für SMP-Systeme oder SpeedStep? "
Bernard



9

Es heißt also explizit Mikrosekunden, aber die Auflösung der Systemuhr ist nicht spezifiziert. Ich nehme an, Auflösung bedeutet in diesem Zusammenhang, wie klein der Betrag ist, der jemals erhöht wird.

Die Datenstruktur hat Mikrosekunden als Maßeinheit, aber das bedeutet nicht, dass die Uhr oder das Betriebssystem tatsächlich in der Lage sind, diese fein zu messen.

Wie andere Leute vorgeschlagen haben, gettimeofday()ist es schlecht, weil das Einstellen der Zeit zu einem Zeitversatz führen und Ihre Berechnung beeinträchtigen kann. clock_gettime(CLOCK_MONOTONIC)ist, was Sie wollen, und clock_getres()wird Ihnen die Präzision Ihrer Uhr sagen.


Was passiert also in Ihrem Code, wenn gettimeofday () mit Sommerzeit vorwärts oder rückwärts springt?
mpez0

3
clock_gettime ist nur unter dem neuesten Linux verfügbar. andere Systeme haben nur gettimeofday ()
vitaly.v.ch

8

Die tatsächliche Auflösung von gettimeofday () hängt von der Hardwarearchitektur ab. Intel-Prozessoren sowie SPARC-Maschinen bieten hochauflösende Timer, die Mikrosekunden messen. Andere Hardwarearchitekturen greifen auf den Timer des Systems zurück, der normalerweise auf 100 Hz eingestellt ist. In solchen Fällen ist die Zeitauflösung weniger genau.

Diese Antwort erhielt ich von High Resolution Time Measurement and Timers, Teil I.


6

Diese Antwort erwähnt Probleme mit der Einstellung der Uhr. Sowohl Ihre Probleme bei der Garantie von Tick-Einheiten als auch die Probleme mit der Zeitanpassung werden in C ++ 11 mit der <chrono>Bibliothek gelöst .

Die Uhr std::chrono::steady_clockwird garantiert nicht angepasst und bewegt sich außerdem relativ zur Echtzeit mit einer konstanten Geschwindigkeit, sodass Technologien wie SpeedStep sie nicht beeinflussen dürfen.

Sie können typsichere Einheiten erhalten, indem Sie in eine der std::chrono::durationSpezialisierungen konvertieren , z std::chrono::microseconds. Bei diesem Typ gibt es keine Mehrdeutigkeit hinsichtlich der vom Tick-Wert verwendeten Einheiten. Beachten Sie jedoch, dass die Uhr nicht unbedingt diese Auflösung hat. Sie können eine Dauer in Attosekunden umwandeln, ohne eine so genaue Uhr zu haben.


4

Aus meiner Erfahrung und aus dem, was ich im Internet gelesen habe, lautet die Antwort "Nein", dies ist nicht garantiert. Dies hängt von der CPU-Geschwindigkeit, dem Betriebssystem, der Linux-Version usw. ab.


3

Das Lesen des RDTSC ist in SMP-Systemen nicht zuverlässig, da jede CPU ihren eigenen Zähler verwaltet und nicht garantiert wird, dass jeder Zähler in Bezug auf eine andere CPU synchronisiert wird.

Ich könnte vorschlagen, es zu versuchen clock_gettime(CLOCK_REALTIME). Das posix-Handbuch gibt an, dass dies auf allen kompatiblen Systemen implementiert werden sollte. Es kann eine Nanosekundenzahl liefern, aber Sie sollten wahrscheinlich clock_getres(CLOCK_REALTIME)Ihr System überprüfen , um die tatsächliche Auflösung zu ermitteln.


clock_getres(CLOCK_REALTIME)wird nicht die wirkliche Auflösung geben. Es gibt immer "1 ns" (eine Nanosekunde) zurück, wenn Zeitgeber verfügbar sind. Überprüfen Sie die include/linux/hrtimer.hDatei auf define HIGH_RES_NSEC 1(mehr unter stackoverflow.com/a/23044075/196561 )
osgx
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.