Come misurare il tempo in millisecondi usando ANSI C?


127

Utilizzando solo ANSI C, esiste un modo per misurare il tempo con precisione in millisecondi o più? Stavo navigando time.h ma ho trovato solo funzioni di seconda precisione.


4
Nota la differenza tra precisione e accuratezza. Puoi ottenere un tempo con precisione in millisecondi prendendo il tempo in secondi e moltiplicandolo per 1000, ma non serve a niente. Le funzioni di precisione ms non hanno necessariamente un'accuratezza ms, anche se generalmente fanno meglio dell'accuratezza 1s.
Steve Jessop,

2
La semplice risposta è NO, ANSI C non supporta la precisione in millisecondi o migliore. La risposta più complessa dipende da cosa stai cercando di fare - francamente l'intera area è un incubo - anche se permetti l'uso delle funzioni Posix ampiamente disponibili. Usi il termine "misura", quindi presumo che tu sia interessato a un intervallo piuttosto che a "orologio da parete". Stai tentando di misurare un periodo di tempo assoluto o l'utilizzo della cpu nel tuo processo?
Astina di livello

2
Volevo solo dire a SOF che mi ha salvato di nuovo la pancetta ;-)
Corlettk,

Risposte:


92

Non esiste una funzione ANSI C che offra una risoluzione temporale migliore di 1 secondo, ma la funzione POSIX gettimeofdayfornisce una risoluzione di microsecondi. La funzione orologio misura solo la quantità di tempo impiegata per l'esecuzione da un processo e non è precisa su molti sistemi.

Puoi usare questa funzione in questo modo:

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

Questo ritorna Time elapsed: 1.000870sulla mia macchina.


30
Leggero avvertimento: gettimeofday () non è monotonico, il che significa che può saltare (e persino tornare indietro) se, ad esempio, la macchina sta tentando di mantenere la sincronizzazione con un server orario di rete o qualche altra fonte temporale.
Astina di livello

3
Per essere precisi: In ISO C99 (che credo sia compatibile in questa parte con ANSI C) non v'è nemmeno una garanzia di qualsiasi risoluzione temporale. (ISO C99, 7.23.1p4)
Roland Illig,

6
Vale la pena notare che timeval::tv_usecè sempre meno di un secondo, è in loop. Vale a dire, al fine di prendere differenze di tempo superiori a 1 secondo, è necessario:long usec_diff = (e.tv_sec - s.tv_sec)*1000000 + (e.tv_usec - s.tv_usec);
Alexander Malakhov

4
@Dipstick: Ma nota che ad esempio NTP non sposta mai l'orologio all'indietro fino a quando non gli dici esplicitamente di farlo.
thejh

1
La logica di sottrazione temporale @AlexanderMalakhov è incapsulata all'interno della timersubfunzione. Possiamo usare i tval_resultvalori (tv_sec e tv_usec) così come sono.
x4444

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

CLOCKS_PER_SEC è impostato su 1000000 su molti sistemi. Stampa il suo valore per essere sicuro prima di usarlo in questo modo.

16
Poiché sono clock al secondo, non importa quale sia il valore, il valore risultante da clock () / CLOCKS_PER_SEC sarà in secondi (almeno dovrebbe essere). La divisione per 1000 lo trasforma in millisecondi.
David Young,

14
Secondo il Manuale di riferimento C, i valori di clock_t possono concludersi a partire da circa 36 minuti. Se stai misurando un calcolo lungo devi esserne consapevole.
CyberSkull,

4
Attenzione anche che la divisione intera CLOCKS_PER_SEC / 1000potrebbe essere inesatta, il che potrebbe influire sul risultato finale (sebbene nella mia esperienza CLOCKS_PER_SECsia sempre stato un multiplo di 1000). Fare (1000 * clock()) / CLOCKS_PER_SECè meno suscettibile all'inesattezza della divisione, ma d'altra parte è più suscettibile all'overflow. Solo alcuni problemi da considerare.
Cornstalks,

4
Questo non misura il tempo della CPU e non il tempo della parete?
krs013,

28

Uso sempre la funzione clock_gettime (), restituendo il tempo dall'orologio CLOCK_MONOTONIC. Il tempo restituito è la quantità di tempo, in secondi e nanosecondi, dal momento che alcuni punti non specificati nel passato, come l'avvio del sistema dell'epoca.

#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 () non è ANSI C.
PowerApp101,

1
Inoltre CLOCK_MONOTONIC non è implementato su molti sistemi (incluse molte piattaforme Linux).
Astina di livello

2
@ PowerApp101 Non esiste un modo ANSI C buono / robusto per farlo. Molte delle altre risposte si basano su POSIX piuttosto che su ANCI C. Detto questo, credo che oggi. @Dipstick Oggi, credo che la maggior parte delle piattaforme moderne [citazione necessaria] supporta clock_gettime(CLOCK_MONOTONIC, ...)e c'è anche la macro di test delle funzionalità _POSIX_MONOTONIC_CLOCK.
omninonsense,

22

Implementazione di una soluzione portatile

Poiché è stato già menzionato qui che non esiste una soluzione ANSI adeguata con sufficiente precisione per il problema di misurazione del tempo, voglio scrivere sui modi in cui ottenere una soluzione di misurazione del tempo portatile e, se possibile, ad alta risoluzione.

Orologio monotonico vs. timestamp

In generale, ci sono due modi per misurare il tempo:

  • orologio monotonico;
  • timestamp corrente (data).

Il primo utilizza un contatore di clock monotonico (a volte viene chiamato contatore di tick) che conta i tick con una frequenza predefinita, quindi se si dispone di un valore di tick e la frequenza è nota, è possibile convertire facilmente i tick in tempo trascorso. In realtà non è garantito che un orologio monotonico rifletta in alcun modo l'ora corrente del sistema, può anche contare i tick dall'avvio del sistema. Ma garantisce che un orologio viene sempre avviato in modo crescente indipendentemente dallo stato del sistema. Di solito la frequenza è legata a una sorgente hardware ad alta risoluzione, ecco perché fornisce un'alta precisione (dipende dall'hardware, ma la maggior parte dell'hardware moderno non ha problemi con le sorgenti di clock ad alta risoluzione).

Il secondo modo fornisce un valore (data) di ora basato sul valore corrente dell'orologio di sistema. Potrebbe anche avere un'alta risoluzione, ma ha un grosso svantaggio: questo tipo di valore dell'ora può essere influenzato da diverse regolazioni dell'ora del sistema, ad esempio cambio di fuso orario, ora legale (DST), aggiornamento del server NTP, ibernazione del sistema e così via sopra. In alcune circostanze è possibile ottenere un valore di tempo trascorso negativo che può portare a un comportamento indefinito. In realtà questo tipo di fonte di tempo è meno affidabile del primo.

Quindi la prima regola nella misurazione dell'intervallo di tempo è usare un orologio monotonico, se possibile. Di solito ha un'alta precisione ed è affidabile dal design.

Strategia di fallback

Quando si implementa una soluzione portatile, vale la pena prendere in considerazione una strategia di fallback: utilizzare un orologio monotonico se disponibile e l'approccio di fallback ai timestamp se non è presente un orologio monotonico nel sistema.

finestre

C'è un ottimo articolo chiamato Acquisizione di timestamp ad alta risoluzione su MSDN sulla misurazione del tempo su Windows che descrive tutti i dettagli che potresti avere bisogno di sapere sul supporto software e hardware. Per acquisire un timestamp di alta precisione su Windows è necessario:

  • interrogare una frequenza del timer (tick per secondo) con QueryPerformanceFrequency :

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

    La frequenza del timer è fissata all'avvio del sistema, quindi è necessario ottenerla solo una volta.

  • interrogare il valore attuale di tick con QueryPerformanceCounter :

    LARGE_INTEGER tcounter;
    LARGE_INTEGER tick_value;
    
    if (QueryPerformanceCounter (&tcounter) != 0)
        tick_value = tcounter.QuadPart;
  • ridimensionare le zecche al tempo trascorso, ovvero ai microsecondi:

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

Secondo Microsoft non dovresti avere problemi con questo approccio su Windows XP e versioni successive nella maggior parte dei casi. Ma puoi anche utilizzare due soluzioni di fallback su Windows:

  • GetTickCount fornisce il numero di millisecondi trascorsi dall'avvio del sistema. Si avvolge ogni 49,7 giorni, quindi fai attenzione a misurare intervalli più lunghi.
  • GetTickCount64 è una versione a 64 bit di GetTickCount, ma è disponibile a partire da Windows Vista e versioni successive.

OS X (macOS)

OS X (macOS) ha le sue unità di tempo assoluto Mach che rappresentano un orologio monotonico. Il modo migliore per iniziare è l'articolo di Apple Domande e risposte tecniche QA1398: Mach Absolute Time Units che descrive (con gli esempi di codice) come utilizzare API specifiche di Mach per ottenere tick monotonici. C'è anche una domanda locale a riguardo chiamata alternativa clock_gettime in Mac OS X che alla fine potrebbe lasciare un po 'di confusione su cosa fare con il possibile overflow del valore perché la frequenza del contatore viene utilizzata sotto forma di numeratore e denominatore. Quindi, un breve esempio su come ottenere il tempo trascorso:

  • ottieni il numeratore e il denominatore della frequenza di clock:

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

    Devi farlo solo una volta.

  • interrogare il valore corrente con mach_absolute_time:

    uint64_t tick_value = mach_absolute_time ();
  • ridimensionare le zecche al tempo trascorso, ovvero ai microsecondi, utilizzando numeratore e denominatore precedentemente interrogati:

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

    L'idea principale per prevenire un overflow è ridimensionare i tick per la precisione desiderata prima di utilizzare il numeratore e il denominatore. Poiché la risoluzione iniziale del timer è in nanosecondi, la dividiamo 1000per ottenere microsecondi. Puoi trovare lo stesso approccio usato in time_mac.c di Chromium . Se hai davvero bisogno di una precisione in nanosecondi, considera la lettura di Come posso usare mach_absolute_time senza traboccare? .

Linux e UNIX

La clock_gettimechiamata è il modo migliore per qualsiasi sistema compatibile con POSIX. Può interrogare il tempo da diverse fonti di clock e quello di cui abbiamo bisogno è CLOCK_MONOTONIC. Non tutti i sistemi che dispongono di clock_gettimesupporto CLOCK_MONOTONIC, quindi la prima cosa che devi fare è verificarne la disponibilità:

  • se _POSIX_MONOTONIC_CLOCKè definito su un valore >= 0significa che CLOCK_MONOTONICè disponibile;
  • se _POSIX_MONOTONIC_CLOCKè definito 0significa che è necessario verificare ulteriormente se funziona in fase di esecuzione, suggerisco di usare sysconf:

    #include <unistd.h>
    
    #ifdef _SC_MONOTONIC_CLOCK
    if (sysconf (_SC_MONOTONIC_CLOCK) > 0) {
        /* A monotonic clock presents */
    }
    #endif
  • altrimenti un orologio monotonico non è supportato e dovresti usare una strategia di fallback (vedi sotto).

L'uso di clock_gettimeè piuttosto semplice:

  • ottieni il valore del tempo:

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

    Ho ridotto il tempo a microsecondi qui.

  • calcola la differenza con il valore temporale precedente ricevuto allo stesso modo:

    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 migliore strategia di fallback è usare la gettimeofdaychiamata: non è un monotono, ma offre una buona risoluzione. L'idea è la stessa di con clock_gettime, ma per ottenere un valore temporale dovresti:

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

Ancora una volta, il valore del tempo viene ridotto a microsecondi.

SGI IRIX

IRIX ha la clock_gettimechiamata, ma manca CLOCK_MONOTONIC. Invece ha una propria sorgente di clock monotonica definita come CLOCK_SGI_CYCLEche dovresti usare invece che CLOCK_MONOTONICcon clock_gettime.

Solaris e HP-UX

Solaris ha una propria interfaccia timer ad alta risoluzione gethrtimeche restituisce il valore corrente del timer in nanosecondi. Sebbene siano disponibili le versioni più recenti di Solaris clock_gettime, è possibile attenersi a gethrtimese è necessario supportare le versioni precedenti di Solaris.

L'uso è semplice:

#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 manca clock_gettime, ma supporta gethrtimeche è necessario utilizzare come su Solaris.

BeOS

BeOS ha anche una propria interfaccia timer ad alta risoluzione system_timeche restituisce il numero di microsecondi trascorsi dall'avvio del computer.

Esempio di utilizzo:

#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 ha una propria API per recuperare timestamp di alta precisione:

  • interrogare una frequenza del timer (tick per unità) con DosTmrQueryFreq(per il compilatore GCC):

    #define INCL_DOSPROFILE
    #define INCL_DOSERRORS
    #include <os2.h>
    #include <stdint.h>
    
    ULONG freq;
    
    DosTmrQueryFreq (&freq);
  • interrogare il valore attuale di tick con 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;
    }
  • ridimensionare le zecche al tempo trascorso, ovvero ai microsecondi:

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

Esempio di implementazione

Puoi dare un'occhiata alla libreria di plibsys che implementa tutte le strategie sopra descritte (vedi ptimeprofiler * .c per i dettagli).


"non esiste una soluzione adeguata ANSI con sufficiente precisione per il problema misurazione del tempo": c'è C11 timespec_get: stackoverflow.com/a/36095407/895245
Ciro Santilli郝海东冠状病六四事件法轮功

1
Questo è ancora un modo sbagliato di misurare il tempo di esecuzione del codice. timespec_getnon è monotonico.
Alexander Saprykin,

11

timespec_get dalla C11

Restituisce fino a nanosecondi, arrotondato alla risoluzione dell'implementazione.

Sembra un furto ANSI da POSIX ' clock_gettime.

Esempio: a printfviene eseguito ogni 100 ms su 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;
}

La bozza standard C11 N1570 7.27.2.5 "La funzione timespec_get dice":

Se base è TIME_UTC, il membro tv_sec è impostato sul numero di secondi da quando un'implementazione ha definito l'epoca, troncata su un valore intero e il membro tv_nsec è impostato sul numero integrale di nanosecondi, arrotondato alla risoluzione dell'orologio di sistema. (321)

321) Sebbene un oggetto struct timespec descriva i tempi con una risoluzione di nanosecondi, la risoluzione disponibile dipende dal sistema e può anche essere maggiore di 1 secondo.

C ++ 11 ha anche ottenuto std::chrono::high_resolution_clock: Timer ad alta risoluzione multipiattaforma C ++

implementazione di glibc 2.21

Si può trovare sotto sysdeps/posix/timespec_get.ccome:

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

così chiaramente:

  • solo TIME_UTCè attualmente supportato

  • inoltra a __clock_gettime (CLOCK_REALTIME, ts), che è un'API POSIX: http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html

    Linux x86-64 ha una clock_gettimechiamata di sistema.

    Si noti che questo non è un metodo di micro-benchmarking a prova di errore perché:

    • man clock_gettimeafferma che questa misura può presentare discontinuità se si modifica l'impostazione dell'ora di sistema durante l'esecuzione del programma. Questo dovrebbe essere un evento raro, ovviamente, e potresti essere in grado di ignorarlo.

    • questo misura il tempo di wall, quindi se lo scheduler decide di dimenticare l'attività, sembrerà funzionare più a lungo.

    Per questi motivi getrusage()potrebbe essere uno strumento di benchmark POSIX migliore, nonostante la precisione massima del microsecondo inferiore.

    Maggiori informazioni su: Misura il tempo in Linux - tempo vs orologio vs getrusage vs clock_gettime vs gettimeofday vs timespec_get?


questa è la risposta giusta a partire dal 2017, anche MSVC ha questa funzione; in termini di benchmarking cercare qualcosa che legga il registro dei chip (nuove versioni dei processori x86 con estensioni PT e le corrispondenti versioni più recenti di Linux kernel / perf)

4

La migliore precisione che puoi eventualmente ottenere è attraverso l'uso dell'istruzione "rdtsc" solo x86, che può fornire una risoluzione a livello di clock (ne deve ovviamente tenere conto del costo della chiamata rdtsc stessa, che può essere facilmente misurata su avvio dell'applicazione).

Il problema principale qui è misurare il numero di orologi al secondo, che non dovrebbe essere troppo difficile.


3
Potrebbe anche essere necessario preoccuparsi dell'affinità del processore, poiché su alcuni computer potresti inviare le chiamate RDTSC a più di un processore e i relativi contatori RDTSC potrebbero non essere sincronizzati.
Will Dean il

1
Inoltre, alcuni processori non hanno un TSC monotonicamente crescente: si pensi a modalità di risparmio energetico che riducono la frequenza della CPU. L'uso di RDTSC per qualsiasi cosa tranne tempi localizzati molto brevi è un'idea MOLTO negativa.
snemarch,

A proposito, la deriva del core menzionata da @WillDean e l'utilizzo di rdtsc per il timing è il motivo per cui un certo numero di giochi non ha funzionato su (presto?) CPU AMD64 multi-core - ho dovuto limitare l'affinità single-core sul mio x2 4400+ per un numero di titoli.
Snemarch,

2

La risposta accettata è abbastanza buona, ma la mia soluzione è più semplice. Ho appena testato in Linux, uso gcc (Ubuntu 7.2.0-8ubuntu3.2) 7.2.0.

Inoltre gettimeofday, tv_secè la parte del secondo e tv_usecè microsecondi , non millisecondi .

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

Stampa:

1522139691342 1522139692342, esattamente un secondo.


-4

Sotto windows:

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

1
è questo ansi C come richiesto?
Gyom,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.