web-dev-qa-db-fra.com

Comment mesurer le temps en millisecondes avec ANSI C?

En utilisant uniquement la norme ANSI C, existe-t-il un moyen de mesurer le temps avec une précision de quelques millisecondes ou plus? Je parcourais time.h mais je n'ai trouvé que des fonctions de seconde précision.

116
corto

Il n’existe pas de fonction ANSI C offrant une résolution temporelle supérieure à 1 seconde, mais la fonction POSIX gettimeofday fournit une résolution en microsecondes. La fonction d'horloge ne mesure que la durée d'exécution d'un processus et n'est pas précise sur de nombreux systèmes.

Vous pouvez utiliser cette fonction comme ceci:

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);

Ceci retourne Time elapsed: 1.000870 sur ma machine.

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

Implémentation d'une solution portable

Comme il a déjà été mentionné ici qu’il n’existe pas de solution ANSI appropriée offrant une précision suffisante pour résoudre le problème de la mesure du temps, je voudrais expliquer comment obtenir une solution de mesure du temps portable et, si possible, à haute résolution.

horloge monotone vs horodatage

En règle générale, il existe deux manières de mesurer le temps:

  • horloge monotone;
  • horodatage actuel (date).

Le premier utilise un compteur d'horloge monotone (parfois appelé compteur de ticks) qui compte les ticks avec une fréquence prédéfinie. Ainsi, si vous avez une valeur de ticks et que la fréquence est connue, vous pouvez facilement convertir les ticks en temps écoulé. En réalité, il n’est pas garanti qu’une horloge monotone reflète l’heure actuelle du système, elle peut également compter les ticks depuis le démarrage du système. Mais cela garantit qu’une horloge est toujours alimentée de manière croissante, quel que soit l’état du système. Habituellement, la fréquence est liée à une source matérielle haute résolution, c'est pourquoi elle offre une grande précision (dépend du matériel, mais la plupart des matériels modernes ne posent aucun problème avec les sources d'horloge haute résolution).

La seconde façon fournit une valeur de date (date) basée sur la valeur d'horloge système actuelle. Il peut également avoir une résolution élevée, mais présente un inconvénient majeur: ce type de valeur de temps peut être affecté par différents réglages de l'heure système, par exemple changement de fuseau horaire, changement d'heure d'été, NTP serveur mise à jour, hibernation du système, etc. Dans certaines circonstances, vous pouvez obtenir une valeur de temps écoulé négative pouvant entraîner un comportement indéfini. En réalité, ce type de source de temps est moins fiable que le premier.

Donc, la première règle dans la mesure d'intervalle de temps est d'utiliser une horloge monotone si possible. Il a généralement une haute précision et il est fiable par conception.

Stratégie de secours

Lors de la mise en œuvre d'une solution portable, il convient de considérer une stratégie de repli: utiliser une horloge monotone si disponible et utiliser l'approche d'horodatage s'il n'y a pas d'horloge monotone dans le système.

Windows

Il existe un excellent article intitulé Acquisition d'horodatages haute résolution sur MSDN concernant la mesure du temps sous Windows, qui décrit tous les détails du support logiciel et matériel dont vous pouvez avoir besoin. Pour obtenir un horodatage de haute précision sous Windows, vous devez:

  • interroger une fréquence de minuteur (ticks par seconde) avec QueryPerformanceFrequency :

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

    La fréquence du minuteur est fixée au démarrage du système, vous ne devez donc l'obtenir qu'une seule fois.

  • interroger la valeur actuelle des ticks avec QueryPerformanceCounter :

    LARGE_INTEGER tcounter;
    LARGE_INTEGER tick_value;
    
    if (QueryPerformanceCounter (&tcounter) != 0)
        tick_value = tcounter.QuadPart;
    
  • redimensionnez les ticks en fonction du temps écoulé, c'est-à-dire en microsecondes:

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

Selon Microsoft, vous ne devriez pas rencontrer de problèmes avec cette approche sous Windows XP et versions ultérieures dans la plupart des cas. Mais vous pouvez également utiliser deux solutions de secours sous Windows:

  • GetTickCount indique le nombre de millisecondes écoulées depuis le démarrage du système. Il se termine tous les 49,7 jours. Veillez donc à mesurer des intervalles plus longs.
  • GetTickCount64 est une version 64 bits de GetTickCount, mais elle est disponible à partir de Windows Vista et des versions ultérieures.

OS X (macOS)

OS X (macOS) possède ses propres unités de temps absolu Mach qui représentent une horloge monotone. La meilleure façon de commencer est l'article d'Apple Questions techniques (QA1398: Unités absolues de Mach qui décrit (avec les exemples de code) comment utiliser l'API spécifique à Mach pour obtenir des ticks monotones. Il y a également une question locale à ce sujet appelée clock_gettime alternative dans Mac OS X qui à la fin peut vous laisser un peu confus quant à la marche à suivre avec le dépassement de valeur possible car la fréquence du compteur est utilisée dans forme de numérateur et dénominateur. Donc, un petit exemple pour obtenir le temps écoulé:

  • obtenir le numérateur et le dénominateur de la fréquence d'horloge:

    #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;
        }
    }
    

    Vous devez le faire une seule fois.

  • interrogez la valeur de tick actuelle avec mach_absolute_time:

    uint64_t tick_value = mach_absolute_time ();
    
  • redimensionnez les graduations en fonction du temps écoulé, c'est-à-dire en microsecondes, en utilisant le numérateur et le dénominateur précédemment interrogés:

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

    L'idée principale pour éviter un débordement est de réduire les graduations à la précision souhaitée avant d'utiliser le numérateur et le dénominateur. Comme la résolution initiale du minuteur est en nanosecondes, nous la divisons par 1000 Pour obtenir des microsecondes. Vous pouvez trouver la même approche que celle utilisée dans Chromium time_mac.c . Si vous avez vraiment besoin d'une précision à la nanoseconde, envisagez de lire le Comment utiliser mach_absolute_time sans déborder? .

Linux et UNIX

L’appel clock_gettime Est votre meilleur moyen sur n’importe quel système compatible POSIX. Il peut interroger l'heure à partir de différentes sources d'horloge, et celle dont nous avons besoin est CLOCK_MONOTONIC. Tous les systèmes qui ont clock_gettime Ne supportent pas CLOCK_MONOTONIC, La première chose à faire est donc de vérifier sa disponibilité:

  • si _POSIX_MONOTONIC_CLOCK est défini sur une valeur >= 0, cela signifie que CLOCK_MONOTONIC est disponible;
  • si _POSIX_MONOTONIC_CLOCK est défini sur 0, cela signifie que vous devez également vérifier si cela fonctionne au moment de l'exécution, je suggère d'utiliser sysconf:

    #include <unistd.h>
    
    #ifdef _SC_MONOTONIC_CLOCK
    if (sysconf (_SC_MONOTONIC_CLOCK) > 0) {
        /* A monotonic clock presents */
    }
    #endif
    
  • sinon, une horloge monotone n'est pas prise en charge et vous devez utiliser une stratégie de repli (voir ci-dessous).

L'utilisation de clock_gettime Est assez simple:

  • obtenir la valeur temporelle:

    #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;
    }
    

    J'ai réduit le temps à quelques microsecondes ici.

  • calculer la différence avec la valeur d'heure précédente reçue de la même manière:

    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;
    

La meilleure stratégie de repli consiste à utiliser l’appel gettimeofday: ce n’est pas un monotone, mais il offre une assez bonne résolution. L'idée est la même qu'avec clock_gettime, Mais pour obtenir une valeur de temps, vous devriez:

#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;
}

Là encore, la valeur de temps est réduite à quelques microsecondes.

SGI IRIX

[~ # ~] irix [~ # ~] a l'appel clock_gettime, mais il lui manque CLOCK_MONOTONIC. Au lieu de cela, il a sa propre source d'horloge monotone définie comme CLOCK_SGI_CYCLE Que vous devriez utiliser à la place de CLOCK_MONOTONIC Avec clock_gettime.

Solaris et HP-UX

Solaris possède sa propre interface de temporisateur haute résolution gethrtime qui renvoie la valeur actuelle du temporisateur en nanosecondes. Bien que les versions les plus récentes de Solaris puissent avoir clock_gettime, Vous pouvez vous en tenir à gethrtime si vous devez prendre en charge les anciennes versions de Solaris.

L'utilisation est simple:

#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;
}

HP-UX manque de clock_gettime, Mais il prend en charge gethrtime que vous devez utiliser de la même manière que sous Solaris.

BeOS

BeOS possède également sa propre interface de minuterie haute résolution system_time, Qui renvoie le nombre de microsecondes écoulées depuis le démarrage de l'ordinateur.

Exemple d'utilisation:

#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 possède sa propre API permettant de récupérer des horodatages haute précision:

  • interroger une fréquence de minuterie (ticks par unité) avec DosTmrQueryFreq (pour le compilateur GCC):

    #define INCL_DOSPROFILE
    #define INCL_DOSERRORS
    #include <os2.h>
    #include <stdint.h>
    
    ULONG freq;
    
    DosTmrQueryFreq (&freq);
    
  • interroger la valeur actuelle des ticks avec 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;
    }
    
  • redimensionnez les ticks en fonction du temps écoulé, c'est-à-dire en microsecondes:

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

Exemple de mise en oeuvre

Vous pouvez consulter la bibliothèque plibsys qui implémente toutes les stratégies décrites ci-dessus (voir ptimeprofiler * .c pour plus de détails).

19
Alexander Saprykin

timespec_get À partir de C11

Retourne en nanosecondes, arrondi à la résolution de l'implémentation.

Ressemble à une arnaque ANSI à partir de clock_gettime De POSIX.

Exemple: un printf est effectué toutes les 100 ms sur Ubuntu 15.10:

#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;
}

Le brouillon standard C11 N157 7.27.2.5 "La fonction timespec_get dit":

Si la valeur de base est TIME_UTC, le membre tv_sec est défini sur le nombre de secondes depuis une époque définie par l'implémentation, tronquée en valeur entière et le membre tv_nsec est défini sur le nombre entier de nanosecondes, arrondi à la résolution de l'horloge système. (321)

321) Bien qu'un objet struct timespec décrit les durées avec une résolution en nanosecondes, la résolution disponible dépend du système et peut même être supérieure à 1 seconde.

C++ 11 a également reçu std::chrono::high_resolution_clock: minuterie haute résolution multiplate-forme C++

implémentation de la glibc 2.21

Peut être trouvé sous sysdeps/posix/timespec_get.c comme:

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;
}

si clairement:

  • seul TIME_UTC est actuellement pris en charge

  • elle est transmise à __clock_gettime (CLOCK_REALTIME, ts), qui est une API POSIX: http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html

    Linux x86-64 a un appel système clock_gettime.

    Notez qu'il ne s'agit pas d'une méthode de micro-analyse comparative infaillible car:

    • man clock_gettime Indique que cette mesure peut avoir des discontinuités si vous modifiez un réglage de l'heure du système pendant l'exécution de votre programme. Cela devrait être un événement rare, bien sûr, et vous pourrez peut-être l'ignorer.

    • cela mesure le temps de mur, donc si le planificateur décide d'oublier votre tâche, celle-ci semblera fonctionner plus longtemps.

    Pour ces raisons, getrusage() pourrait être un meilleur outil d'analyse comparative POSIX, malgré une précision maximale inférieure à la microseconde.

    Plus d'informations sur: Mesurer le temps sous Linux - heure vs horloge vs getrusage vs horloge_gettime vs gettimeofday vs timespec_get?

La meilleure précision que vous puissiez obtenir consiste à utiliser l’instruction "rdtsc" pour x86 uniquement, qui peut fournir une résolution au niveau de l’horloge (il faut bien sûr prendre en compte le coût de l’appel rdtsc lui-même, qui peut être mesuré facilement démarrage de l'application).

Le principal problème ici est de mesurer le nombre d’horloges par seconde, ce qui ne devrait pas être trop difficile.

4
Dark Shikari

La réponse acceptée est assez bonne.Mais ma solution est plus simple.Je viens de tester sous Linux, utilisez gcc (Ubuntu 7.2.0-8ubuntu3.2) 7.2.0.

Alse utilise gettimeofday, le tv_sec est la partie de second et le tv_usec est microsecondes, pas millisecondes.

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;
 }

Il imprime:

1522139691342 1522139692342, exactement une seconde.

1
Lee