Comment mesurer le temps en millisecondes en utilisant ANSI C?


127

En utilisant uniquement 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.


4
Notez la différence entre la précision et l'exactitude. Vous pouvez obtenir un temps avec une précision de la milliseconde en prenant le temps en secondes et en le multipliant par 1000, mais cela ne sert à rien. Les fonctions de précision ms n'ont pas nécessairement une précision de ms - bien qu'elles aient généralement une précision supérieure à 1s.
Steve Jessop

2
La réponse simple est NON, ANSI C ne prend pas en charge la précision milliseconde ou mieux. La réponse la plus complexe dépend de ce que vous essayez de faire - franchement, toute la zone est un cauchemar - même si vous autorisez l'utilisation des fonctions Posix largement disponibles. Vous utilisez le terme «mesure», donc je suppose que vous êtes intéressé par un intervalle plutôt que par une «horloge murale». Mais essayez-vous de mesurer une période de temps absolue ou une utilisation du processeur par votre processus?
Jauge de niveau

2
Je voulais juste dire à SOF que je viens de sauver mon bacon, encore une fois ;-)
corlettk

Réponses:


92

Il n'y a pas de fonction ANSI C qui offre une résolution temporelle supérieure à 1 seconde, mais la fonction POSIX gettimeofdayfournit une résolution en microsecondes. La fonction d'horloge ne mesure que le temps qu'un processus a passé à s'exécuter 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);

Cela revient Time elapsed: 1.000870sur ma machine.


30
Légère mise en garde: gettimeofday () n'est pas monotone, ce qui signifie qu'il peut sauter (et même reculer) si, par exemple, votre machine tente de maintenir la synchronisation avec un serveur de temps réseau ou une autre source de temps.
Jauge de niveau

3
Pour être précis: Dans ISO C99 (qui je pense est compatible dans cette partie à la norme ANSI C) il n'y a même pas une garantie de toute résolution temporelle. (ISO C99, 7.23.1p4)
Roland Illig

6
Il est à noter que cela timeval::tv_usecprend toujours moins d'une seconde, c'est en boucle. C'est-à-dire que pour prendre des différences de temps supérieures à 1 s, vous devez:long usec_diff = (e.tv_sec - s.tv_sec)*1000000 + (e.tv_usec - s.tv_usec);
Alexander Malakhov

4
@Dipstick: Mais notez que par exemple NTP ne fait jamais reculer votre horloge tant que vous ne lui demandez pas explicitement de le faire.
thejh

1
La logique de soustraction du temps @AlexanderMalakhov est intégrée à la timersubfonction. Nous pouvons utiliser les tval_resultvaleurs (tv_sec et tv_usec) telles quelles.
x4444

48
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);

CLOCKS_PER_SEC est défini sur 1000000 sur de nombreux systèmes. Imprimez sa valeur pour être sûr avant de l'utiliser de cette manière.
ysap

16
Comme ce sont des horloges par seconde, peu importe la valeur, la valeur résultante de clock () / CLOCKS_PER_SEC sera en secondes (du moins elle devrait l'être). Diviser par 1000 transforme cela en millisecondes.
David Young

14
Selon le manuel de référence C, les valeurs clock_t peuvent s'enrouler à partir d'environ 36 minutes. Si vous mesurez un long calcul, vous devez en être conscient.
CyberSkull

4
Méfiez-vous également que la division entière CLOCKS_PER_SEC / 1000pourrait éventuellement être inexacte, ce qui pourrait affecter le résultat final (bien que dans mon expérience CLOCKS_PER_SECait toujours été un multiple de 1000). Faire (1000 * clock()) / CLOCKS_PER_SECest moins sensible à l'inexactitude de la division, mais d'un autre côté est plus susceptible de déborder. Juste quelques problèmes à considérer.
Cornstalks

4
Cela ne mesure-t-il pas le temps CPU et non le temps mural?
krs013

28

J'utilise toujours la fonction clock_gettime (), renvoyant l'heure de l'horloge CLOCK_MONOTONIC. Le temps renvoyé est la durée, en secondes et en nanosecondes, depuis un moment non spécifié dans le passé, tel que le démarrage du système de l'époque.

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

int64_t timespecDiff(struct timespec *timeA_p, struct timespec *timeB_p)
{
  return ((timeA_p->tv_sec * 1000000000) + timeA_p->tv_nsec) -
           ((timeB_p->tv_sec * 1000000000) + timeB_p->tv_nsec);
}

int main(int argc, char **argv)
{
  struct timespec start, end;
  clock_gettime(CLOCK_MONOTONIC, &start);

  // Some code I am interested in measuring 

  clock_gettime(CLOCK_MONOTONIC, &end);

  uint64_t timeElapsed = timespecDiff(&end, &start);
}

5
clock_gettime () n'est pas ANSI C.
PowerApp101

1
CLOCK_MONOTONIC n'est pas non plus implémenté sur de nombreux systèmes (y compris de nombreuses plates-formes Linux).
Dipstick

2
@ PowerApp101 Il n'y a pas de moyen ANSI C bon / robuste de faire cela. Beaucoup d'autres réponses reposent sur POSIX plutôt que sur ANCI C. Cela étant dit, je le crois aujourd'hui. @Dipstick Aujourd'hui, je crois que la plupart des plates-formes modernes [la citation nécessaire] prennent en charge clock_gettime(CLOCK_MONOTONIC, ...)et il y a même la macro de test de fonctionnalités _POSIX_MONOTONIC_CLOCK.
omninonsense

22

Mettre en œuvre une solution portable

Comme il a déjà été mentionné ici qu'il n'y a pas de solution ANSI appropriée avec une précision suffisante pour le problème de la mesure du temps, je veux écrire sur les moyens d'obtenir une solution de mesure du temps portable et, si possible, à haute résolution.

Horloge monotone et horodatage

De manière générale, il existe deux méthodes de mesure du 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, donc si vous avez une valeur de ticks et que la fréquence est connue, vous pouvez facilement convertir les ticks en temps écoulé. Il n'est en fait pas garanti qu'une horloge monotone reflète en aucune façon l'heure actuelle du système, elle peut également compter les ticks depuis un démarrage du système. Mais cela garantit qu'une horloge est toujours exécuté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 du matériel moderne n'a aucun problème avec les sources d'horloge haute résolution).

La seconde méthode fournit une valeur d'heure (date) basée sur la valeur actuelle de l'horloge système. Il peut également avoir une résolution élevée, mais il présente un inconvénient majeur: ce type de valeur d'heure peut être affecté par différents ajustements de l'heure du système, c'est-à-dire le changement de fuseau horaire, le changement d'heure d'été (DST), la mise à jour du serveur NTP, l'hibernation du système, etc. sur. Dans certaines circonstances, vous pouvez obtenir une valeur de temps écoulé négative qui peut conduire à un comportement indéfini. En fait, ce type de source horaire est moins fiable que la première.

Ainsi, 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 est fiable de par sa conception.

Stratégie de repli

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

les fenêtres

Il existe un excellent article intitulé Acquérir des horodatages haute résolution sur MSDN sur la mesure du temps sous Windows, qui décrit tous les détails que vous pourriez avoir besoin de connaître sur la prise en charge logicielle et matérielle. Pour acquérir un horodatage de haute précision sous Windows, vous devez:

  • interroger une fréquence de minuterie (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 n'avez donc besoin de l'obtenir qu'une seule fois.

  • interrogez la valeur actuelle des graduations avec QueryPerformanceCounter :

    LARGE_INTEGER tcounter;
    LARGE_INTEGER tick_value;
    
    if (QueryPerformanceCounter (&tcounter) != 0)
        tick_value = tcounter.QuadPart;
  • mettre à l'échelle les graduations 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 avoir aucun problème avec cette approche sur Windows XP et les versions ultérieures dans la plupart des cas. Mais vous pouvez également utiliser deux solutions de secours sous Windows:

  • GetTickCount fournit le nombre de millisecondes qui se sont écoulées depuis le démarrage du système. Il s'enroule tous les 49,7 jours, alors soyez prudent en mesurant des intervalles plus longs.
  • GetTickCount64 est une version 64 bits de GetTickCount, mais elle est disponible à partir de Windows Vista et supérieur.

OS X (macOS)

OS X (macOS) a ses propres unités de temps absolues Mach qui représentent une horloge monotone. La meilleure façon de commencer est l'article d'Apple Q&R technique QA1398: Mach Absolute Time Units qui décrit (avec les exemples de code) comment utiliser l'API spécifique à Mach pour obtenir des ticks monotones. Il y a aussi une question locale à ce sujet appelée alternative clock_gettime dans Mac OS X qui à la fin peut vous laisser un peu confus quoi faire avec le dépassement de valeur possible car la fréquence du compteur est utilisée sous la forme de numérateur et de dénominateur. Donc, un petit exemple comment 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 ne devez le faire qu'une seule fois.

  • interroger la valeur de graduation actuelle avec mach_absolute_time:

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

    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 de la minuterie est en nanosecondes, nous la divisons par 1000pour obtenir des microsecondes. Vous pouvez trouver la même approche utilisée dans time_mac.c de Chromium . Si vous avez vraiment besoin d'une précision à la nanoseconde, envisagez de lire le Comment puis-je utiliser mach_absolute_time sans déborder? .

Linux et UNIX

L' clock_gettimeappel 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 ne sont pas pris en clock_gettimecharge CLOCK_MONOTONIC, la première chose à faire est donc de vérifier sa disponibilité:

  • si _POSIX_MONOTONIC_CLOCKest défini sur une valeur, >= 0cela signifie qu'il CLOCK_MONOTONICest disponible;
  • si elle _POSIX_MONOTONIC_CLOCKest définie, 0cela signifie que vous devez en outre 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_gettimeest assez simple:

  • obtenir la valeur de temps:

    #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 en microsecondes ici.

  • calculer la différence avec la valeur de temps 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 est d'utiliser l' gettimeofdayappel: 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 devez:

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

Encore une fois, la valeur de temps est réduite en microsecondes.

SGI IRIX

IRIX a l' clock_gettimeappel, mais il en manque CLOCK_MONOTONIC. Au lieu de cela, il a sa propre source d'horloge monotone définie que CLOCK_SGI_CYCLEvous devez utiliser au lieu de CLOCK_MONOTONICavec clock_gettime.

Solaris et HP-UX

Solaris possède sa propre interface de minuterie haute résolution gethrtimequi renvoie la valeur actuelle du minuteur en nanosecondes. Bien que les nouvelles versions de Solaris puissent avoir clock_gettime, vous pouvez vous en tenir àgethrtime si vous avez besoin de 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 clock_gettime, mais il prend en charge gethrtimece que vous devez utiliser de la même manière que sur Solaris.

BeOS

BeOS possède également sa propre interface de minuterie haute résolution system_timequi 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 pour récupérer des horodatages de 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 graduations 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;
    }
  • mettre à l'échelle les graduations en fonction du temps écoulé, c'est-à-dire en microsecondes:

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

Exemple d'implémentation

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


"il n'y a pas de solution ANSI appropriée avec une précision suffisante pour le problème de mesure du temps": il y a C11 timespec_get : stackoverflow.com/a/36095407/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
C'est toujours une mauvaise façon de mesurer le temps d'exécution du code. timespec_getn'est pas monotone.
Alexander Saprykin

11

timespec_get à partir de C11

Renvoie jusqu'à nanosecondes, arrondi à la résolution de l'implémentation.

On dirait une arnaque ANSI de POSIX ' clock_gettime .

Exemple: a printfest 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 projet de norme 7.27.2.5 C11 N1570 "La fonction timespec_get dit":

Si 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é à une 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écrive des temps avec une résolution nanoseconde, la résolution disponible dépend du système et peut même être supérieure à 1 seconde.

C ++ 11 a également obtenu std::chrono::high_resolution_clock: Minuterie haute résolution multiplateforme 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:

  • seulement TIME_UTC est actuellement pris en charge

  • il transmet à __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 clock_gettime appel système.

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

    • man clock_gettimeindique que cette mesure peut présenter des discontinuités si vous modifiez certains paramètres d'heure système pendant l'exécution de votre programme. Cela devrait bien sûr être un événement rare et vous pourrez peut-être l'ignorer.

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

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

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


c'est la bonne réponse à partir de 2017, même MSVC a cette fonction; en termes de benchmarking, recherchez quelque chose qui lit le registre de la puce (les nouvelles versions des processeurs x86 avec les extensions PT, et les nouvelles versions correspondantes du noyau Linux / perf)

4

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

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


3
Vous devrez peut-être également vous soucier de l'affinité du processeur, car sur certaines machines, vous pouvez envoyer les appels RDTSC à plusieurs processeurs et leurs compteurs RDTSC peuvent ne pas être synchronisés.
Will Dean

1
De plus, certains processeurs n'ont pas de TSC à augmentation monotone - pensez aux modes d'économie d'énergie qui réduisent la fréquence du processeur. Utiliser RDTSC pour autre chose que des temps localisés très courts est une TRÈS mauvaise idée.
snemarch

Btw, la dérive du noyau mentionnée par @WillDean et l'utilisation de rdtsc pour le chronométrage est la raison pour laquelle un certain nombre de jeux ne fonctionnaient pas sur les processeurs AMD64 multicœurs (au début?) - j'ai dû me limiter à l'affinité monocœur sur mon x2 4400+ pour un certain nombre de titres.
snemarch

2

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 use gettimeofday, the tv_secis the part of second, and the tv_usecis microsecondes , not 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.


-4

Sous windows:

SYSTEMTIME t;
GetLocalTime(&t);
swprintf_s(buff, L"[%02d:%02d:%02d:%d]\t", t.wHour, t.wMinute, t.wSecond, t.wMilliseconds);

1
est-ce ansi C comme demandé?
Gyom
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.