it-swarm.com.de

Wie kann man mit ANSI C die Zeit in Millisekunden messen?

Gibt es eine Möglichkeit, die Zeit nur mit ANSI C millisekundengenau oder schneller zu messen? Ich habe time.h durchsucht, aber nur Funktionen mit zweiter Genauigkeit gefunden.

116
corto

Es gibt keine ANSI C-Funktion, die eine bessere Auflösung als 1 Sekunde bietet, aber die POSIX-Funktion gettimeofday liefert eine Mikrosekundenauflösung. Die Uhrfunktion misst nur die Zeit, die ein Prozess ausgeführt hat, und ist auf vielen Systemen nicht genau.

Sie können diese Funktion wie folgt verwenden:

struct timeval tval_before, tval_after, tval_result;

gettimeofday(&tval_before, NULL);

// Some code you want to time, for example:
sleep(1);

gettimeofday(&tval_after, NULL);

timersub(&tval_after, &tval_before, &tval_result);

printf("Time elapsed: %ld.%06ld\n", (long int)tval_result.tv_sec, (long int)tval_result.tv_usec);

Dies gibt Time elapsed: 1.000870 auf meiner Maschine.

86
Robert Gamble
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
44
Nick Van Brunt

Implementierung einer portablen Lösung

Da hier bereits erwähnt wurde, dass es keine richtige ANSI-Lösung mit ausreichender Genauigkeit für das Zeitmessproblem gibt, möchte ich darüber schreiben, wie man eine tragbare und möglichst hochauflösende Zeitmesslösung erhält.

Monotone Uhr vs. Zeitstempel

Grundsätzlich gibt es zwei Möglichkeiten der Zeitmessung:

  • monotone Uhr;
  • aktueller (Datums-) Zeitstempel.

Der erste verwendet einen monotonen Taktzähler (manchmal wird er als Tick-Zähler bezeichnet), der Ticks mit einer vordefinierten Frequenz zählt. Wenn Sie also einen Tick-Wert haben und die Frequenz bekannt ist, können Sie Ticks einfach in abgelaufene Zeit umrechnen. Es kann nicht garantiert werden, dass eine monotone Uhr in irgendeiner Weise die aktuelle Systemzeit widerspiegelt. Sie kann auch Ticks seit dem Systemstart zählen. Sie garantiert aber, dass eine Uhr unabhängig vom Systemzustand immer weiter hochgefahren wird. Normalerweise ist die Frequenz an eine hochauflösende Hardwarequelle gebunden, weshalb sie eine hohe Genauigkeit bietet (abhängig von der Hardware, aber die meisten modernen Hardware-Geräte haben keine Probleme mit hochauflösenden Taktquellen).

Der zweite Weg liefert einen (Datums-) Zeitwert basierend auf dem aktuellen Systemuhrwert. Es kann auch eine hohe Auflösung haben, hat aber einen großen Nachteil: Diese Art von Zeitwert kann durch verschiedene Systemzeitanpassungen beeinflusst werden, z. B. Zeitzonenänderung, Sommerzeitänderung, NTP Server Update, Ruhezustand des Systems und so weiter. Unter bestimmten Umständen können Sie einen negativen Wert für die abgelaufene Zeit erhalten, der zu einem undefinierten Verhalten führen kann. Tatsächlich ist diese Art von Zeitquelle weniger zuverlässig als die erste.

Die erste Regel bei der Zeitintervallmessung ist daher, wenn möglich eine monotone Uhr zu verwenden. Es hat normalerweise eine hohe Präzision und ist konstruktionsbedingt zuverlässig.

Fallback-Strategie

Bei der Implementierung einer tragbaren Lösung sollte eine Ausweichstrategie in Betracht gezogen werden: Verwenden Sie eine monotone Uhr, falls verfügbar, und greifen Sie auf Zeitstempel zurück, wenn keine monotone Uhr im System vorhanden ist.

Windows

Es gibt einen großartigen Artikel mit dem Titel Erfassung von hochauflösenden Zeitstempeln auf MSDN über die Zeitmessung unter Windows, in dem alle Details beschrieben werden, die Sie möglicherweise über Software- und Hardware-Support wissen müssen. Um unter Windows einen hochpräzisen Zeitstempel zu erhalten, sollten Sie:

  • abfrage einer Timer-Frequenz (Ticks pro Sekunde) mit QueryPerformanceFrequency :

    LARGE_INTEGER tcounter;
    LARGE_INTEGER freq;    
    
    if (QueryPerformanceFrequency (&tcounter) != 0)
        freq = tcounter.QuadPart;
    

    Die Timer-Frequenz ist beim Systemstart festgelegt, sodass Sie sie nur einmal abrufen müssen.

  • abfrage des aktuellen Tickwertes mit QueryPerformanceCounter :

    LARGE_INTEGER tcounter;
    LARGE_INTEGER tick_value;
    
    if (QueryPerformanceCounter (&tcounter) != 0)
        tick_value = tcounter.QuadPart;
    
  • skalieren Sie die Ticks auf die verstrichene Zeit, d. h. auf Mikrosekunden:

    LARGE_INTEGER usecs = (tick_value - prev_tick_value) / (freq / 1000000);
    

Laut Microsoft sollten Sie mit dieser Vorgehensweise in den meisten Fällen unter Windows XP und höheren Versionen keine Probleme haben. Sie können aber auch zwei Fallback-Lösungen unter Windows verwenden:

  • GetTickCount gibt die Anzahl der Millisekunden an, die seit dem Start des Systems vergangen sind. Es wird alle 49,7 Tage gewickelt. Seien Sie also vorsichtig, wenn Sie längere Intervalle messen.
  • GetTickCount64 ist eine 64-Bit-Version von GetTickCount, die jedoch ab Windows Vista verfügbar ist.

OS X (macOS)

OS X (macOS) hat seine eigenen absoluten Mach-Zeiteinheiten, die eine monotone Uhr darstellen. Der beste Einstieg ist der Artikel von Apple Technische QA QA1398: Mach-Absolute-Zeit-Einheiten , in dem (mit den Codebeispielen) beschrieben wird, wie man eine Mach-spezifische API verwendet, um monotone Ticks zu erhalten. Es gibt auch eine lokale Frage namens clock_gettime alternative in Mac OS X , die Sie am Ende etwas verwirren lässt, was Sie mit dem möglichen Wertüberlauf tun sollen, da die Zählerfrequenz in der verwendet wird Form von Zähler und Nenner. Also, ein kurzes Beispiel, wie man die verstrichene Zeit bekommt:

  • holen Sie sich den Taktfrequenzzähler und Nenner:

    #include <mach/mach_time.h>
    #include <stdint.h>
    
    static uint64_t freq_num   = 0;
    static uint64_t freq_denom = 0;
    
    void init_clock_frequency ()
    {
        mach_timebase_info_data_t tb;
    
        if (mach_timebase_info (&tb) == KERN_SUCCESS && tb.denom != 0) {
            freq_num   = (uint64_t) tb.numer;
            freq_denom = (uint64_t) tb.denom;
        }
    }
    

    Sie müssen das nur einmal tun.

  • abfrage des aktuellen Tick-Wertes mit mach_absolute_time:

    uint64_t tick_value = mach_absolute_time ();
    
  • skalieren Sie die Ticks auf die verstrichene Zeit, d. h. auf Mikrosekunden, unter Verwendung des zuvor abgefragten Zählers und Nenners:

    uint64_t value_diff = tick_value - prev_tick_value;
    
    /* To prevent overflow */
    value_diff /= 1000;
    
    value_diff *= freq_num;
    value_diff /= freq_denom;
    

    Die Hauptidee, um einen Überlauf zu verhindern, besteht darin, die Teilstriche auf die gewünschte Genauigkeit zu verkleinern, bevor Zähler und Nenner verwendet werden. Da die anfängliche Auflösung des Timers in Nanosekunden angegeben ist, teilen wir sie durch 1000, Um Mikrosekunden zu erhalten. Sie können den gleichen Ansatz wie in time_mac.c von Chrom finden. Wenn Sie wirklich eine Genauigkeit von Nanosekunden benötigen, lesen Sie die Wie kann ich mach_absolute_time verwenden, ohne überzulaufen? .

Linux und UNIX

Der Aufruf clock_gettime Ist der beste Weg auf jedem POSIX-freundlichen System. Es kann Zeit von verschiedenen Taktquellen abfragen, und die eine, die wir benötigen, ist CLOCK_MONOTONIC. Nicht alle Systeme, die clock_gettime Unterstützen, CLOCK_MONOTONIC. Überprüfen Sie daher zunächst die Verfügbarkeit:

  • wenn _POSIX_MONOTONIC_CLOCK auf einen Wert >= 0 festgelegt ist, bedeutet dies, dass CLOCK_MONOTONIC verfügbar ist.
  • wenn _POSIX_MONOTONIC_CLOCK als 0 definiert ist, bedeutet dies, dass Sie zusätzlich prüfen sollten, ob es zur Laufzeit funktioniert. Ich empfehle, sysconf zu verwenden:

    #include <unistd.h>
    
    #ifdef _SC_MONOTONIC_CLOCK
    if (sysconf (_SC_MONOTONIC_CLOCK) > 0) {
        /* A monotonic clock presents */
    }
    #endif
    
  • andernfalls wird eine monotone Uhr nicht unterstützt und Sie sollten eine Ausweichstrategie verwenden (siehe unten).

Die Verwendung von clock_gettime Ist ziemlich einfach:

  • erhalte den Zeitwert:

    #include <time.h>
    #include <sys/time.h>
    #include <stdint.h>
    
    uint64_t get_posix_clock_time ()
    {
        struct timespec ts;
    
        if (clock_gettime (CLOCK_MONOTONIC, &ts) == 0)
            return (uint64_t) (ts.tv_sec * 1000000 + ts.tv_nsec / 1000);
        else
            return 0;
    }
    

    Ich habe die Zeit hier auf Mikrosekunden verkleinert.

  • berechnen Sie die Differenz zum vorherigen Zeitwert auf die gleiche Weise:

    uint64_t prev_time_value, time_value;
    uint64_t time_diff;
    
    /* Initial time */
    prev_time_value = get_posix_clock_time ();
    
    /* Do some work here */
    
    /* Final time */
    time_value = get_posix_clock_time ();
    
    /* Time difference */
    time_diff = time_value - prev_time_value;
    

Die beste Fallback-Strategie besteht darin, den gettimeofday -Aufruf zu verwenden: Er ist nicht monoton, bietet jedoch eine recht gute Auflösung. Die Idee ist die gleiche wie bei clock_gettime, Aber um einen Zeitwert zu erhalten, sollten Sie:

#include <time.h>
#include <sys/time.h>
#include <stdint.h>

uint64_t get_gtod_clock_time ()
{
    struct timeval tv;

    if (gettimeofday (&tv, NULL) == 0)
        return (uint64_t) (tv.tv_sec * 1000000 + tv.tv_usec);
    else
        return 0;
}

Auch hier wird der Zeitwert auf Mikrosekunden verkleinert.

SGI IRIX

[~ # ~] irix [~ # ~] hat den Aufruf clock_gettime, aber es fehlt CLOCK_MONOTONIC. Stattdessen hat es eine eigene monotone Clock-Quelle, die als CLOCK_SGI_CYCLE Definiert ist und die Sie anstelle von CLOCK_MONOTONIC Mit clock_gettime Verwenden sollten.

Solaris und HP-UX

Solaris verfügt über eine eigene hochauflösende Timer-Schnittstelle gethrtime, die den aktuellen Timer-Wert in Nanosekunden zurückgibt. Obwohl die neueren Versionen von Solaris möglicherweise clock_gettime Haben, können Sie sich an gethrtime halten, wenn Sie alte Solaris-Versionen unterstützen müssen.

Die Verwendung ist einfach:

#include <sys/time.h>

void time_measure_example ()
{
    hrtime_t prev_time_value, time_value;
    hrtime_t time_diff;

    /* Initial time */
    prev_time_value = gethrtime ();

    /* Do some work here */

    /* Final time */
    time_value = gethrtime ();

    /* Time difference */
    time_diff = time_value - prev_time_value;
}

In HP-UX fehlt clock_gettime, Aber es unterstützt gethrtime, das Sie wie unter Solaris verwenden sollten.

BeOS

BeOS hat auch eine eigene hochauflösende Timer-Schnittstelle system_time, Die die Anzahl der Mikrosekunden zurückgibt, die seit dem Booten des Computers vergangen sind.

Anwendungsbeispiel:

#include <kernel/OS.h>

void time_measure_example ()
{
    bigtime_t prev_time_value, time_value;
    bigtime_t time_diff;

    /* Initial time */
    prev_time_value = system_time ();

    /* Do some work here */

    /* Final time */
    time_value = system_time ();

    /* Time difference */
    time_diff = time_value - prev_time_value;
}

OS/2

OS/2 hat eine eigene API, um hochpräzise Zeitstempel abzurufen:

  • abfrage einer Timer-Frequenz (Ticks pro Einheit) mit DosTmrQueryFreq (für GCC-Compiler):

    #define INCL_DOSPROFILE
    #define INCL_DOSERRORS
    #include <os2.h>
    #include <stdint.h>
    
    ULONG freq;
    
    DosTmrQueryFreq (&freq);
    
  • abfrage des aktuellen Tickwertes mit DosTmrQueryTime:

    QWORD    tcounter;
    unit64_t time_low;
    unit64_t time_high;
    unit64_t timestamp;
    
    if (DosTmrQueryTime (&tcounter) == NO_ERROR) {
        time_low  = (unit64_t) tcounter.ulLo;
        time_high = (unit64_t) tcounter.ulHi;
    
        timestamp = (time_high << 32) | time_low;
    }
    
  • skalieren Sie die Ticks auf die verstrichene Zeit, d. h. auf Mikrosekunden:

    uint64_t usecs = (prev_timestamp - timestamp) / (freq / 1000000);
    

Beispielimplementierung

Sie können einen Blick in die Bibliothek plibsys werfen, die alle oben beschriebenen Strategien implementiert (Details siehe ptimeprofiler * .c).

19

timespec_get Von C11

Gibt bis zu Nanosekunden zurück, gerundet auf die Auflösung der Implementierung.

Sieht aus wie eine ANSI-Abzocke von POSIX 'clock_gettime.

Beispiel: Unter Ubuntu 15.10 wird alle 100ms ein printf ausgeführt:

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

static long get_nanos(void) {
    struct timespec ts;
    timespec_get(&ts, TIME_UTC);
    return (long)ts.tv_sec * 1000000000L + ts.tv_nsec;
}

int main(void) {
    long nanos;
    long last_nanos;
    long start;
    nanos = get_nanos();
    last_nanos = nanos;
    start = nanos;
    while (1) {
        nanos = get_nanos();
        if (nanos - last_nanos > 100000000L) {
            printf("current nanos: %ld\n", nanos - start);
            last_nanos = nanos;
        }
    }
    return EXIT_SUCCESS;
}

The C11 N1570 Standard Draft 7.27.2.5 "Die Funktion timespec_get sagt":

Wenn base TIME_UTC ist, wird das Element tv_sec auf die Anzahl der Sekunden seit einer durch die Implementierung definierten Epoche gesetzt, auf einen ganzen Wert gekürzt und das Element tv_nsec auf die ganzzahlige Anzahl von Nanosekunden gesetzt, gerundet auf die Auflösung der Systemuhr. (321)

321) Obwohl ein Struktur-Zeitspezifikationsobjekt Zeiten mit Nanosekundenauflösung beschreibt, ist die verfügbare Auflösung systemabhängig und kann sogar größer als 1 Sekunde sein.

C++ 11 hat auch std::chrono::high_resolution_clock: C++ - Cross-Platform-Timer mit hoher Auflösung

glibc 2.21 Implementierung

Kann unter sysdeps/posix/timespec_get.c gefunden werden als:

int
timespec_get (struct timespec *ts, int base)
{
  switch (base)
    {
    case TIME_UTC:
      if (__clock_gettime (CLOCK_REALTIME, ts) < 0)
        return 0;
      break;

    default:
      return 0;
    }

  return base;
}

so klar:

  • derzeit wird nur TIME_UTC unterstützt

  • es wird an __clock_gettime (CLOCK_REALTIME, ts) weitergeleitet, eine POSIX-API: http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html

    Linux x86-64 hat einen clock_gettime - Systemaufruf.

    Beachten Sie, dass dies keine ausfallsichere Micro-Benchmarking-Methode ist, da:

    • man clock_gettime Gibt an, dass diese Kennzahl Unstetigkeiten aufweisen kann, wenn Sie während der Ausführung Ihres Programms eine Systemzeiteinstellung ändern. Dies sollte natürlich ein seltenes Ereignis sein und Sie können es möglicherweise ignorieren.

    • dies misst die Wandzeit. Wenn der Scheduler also beschließt, Ihre Aufgabe zu vergessen, wird sie offenbar länger ausgeführt.

    Aus diesen Gründen ist getrusage() möglicherweise ein besseres POSIX-Benchmarking-Tool, obwohl die maximale Präzision im Mikrosekundenbereich geringer ist.

    Weitere Informationen unter: Zeit unter Linux messen - Zeit vs Uhr vs Getrusage vs Clock_gettime vs Gettimeofday vs Timespec_get?

Die beste Präzision, die Sie erhalten können, ist die Verwendung des x86-reinen "rdtsc" -Befehls, der eine Auflösung auf Taktebene bietet (ne muss natürlich die Kosten des rdtsc-Aufrufs selbst berücksichtigen, die leicht gemessen werden können Anwendungsstart).

Der wichtigste Haken dabei ist die Messung der Anzahl der Takte pro Sekunde, was nicht zu schwierig sein sollte.

4
Dark Shikari

Die akzeptierte Antwort ist gut genug. Aber meine Lösung ist einfacher. Ich teste nur unter Linux, benutze gcc (Ubuntu 7.2.0-8ubuntu3.2) 7.2.0.

Verwenden Sie auch gettimeofday, das tv_sec ist der Teil von second und der tv_usec ist Mikrosekunden, nicht Millisekunden.

long currentTimeMillis() {
  struct timeval time;
  gettimeofday(&time, NULL);

  return time.tv_sec * 1000 + time.tv_usec / 1000;
}

int main() {
  printf("%ld\n", currentTimeMillis());
  // wait 1 second
  sleep(1);
  printf("%ld\n", currentTimeMillis());
  return 0;
 }

Es drucken:

1522139691342 1522139692342, genau eine Sekunde.

1
Lee