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.
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.
Risposte:
Non esiste una funzione ANSI C che offra una risoluzione temporale migliore di 1 secondo, ma la funzione POSIX gettimeofday
fornisce 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.000870
sulla mia macchina.
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);
timersub
funzione. Possiamo usare i tval_result
valori (tv_sec e tv_usec) così come sono.
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
CLOCKS_PER_SEC / 1000
potrebbe essere inesatta, il che potrebbe influire sul risultato finale (sebbene nella mia esperienza CLOCKS_PER_SEC
sia 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.
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);
}
clock_gettime(CLOCK_MONOTONIC, ...)
e c'è anche la macro di test delle funzionalità _POSIX_MONOTONIC_CLOCK
.
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:
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
, 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 1000
per 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_gettime
chiamata è 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_gettime
supporto CLOCK_MONOTONIC
, quindi la prima cosa che devi fare è verificarne la disponibilità:
_POSIX_MONOTONIC_CLOCK
è definito su un valore >= 0
significa che CLOCK_MONOTONIC
è disponibile;se _POSIX_MONOTONIC_CLOCK
è definito 0
significa 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
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 gettimeofday
chiamata: 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_gettime
chiamata, ma manca CLOCK_MONOTONIC
. Invece ha una propria sorgente di clock monotonica definita come CLOCK_SGI_CYCLE
che dovresti usare invece che CLOCK_MONOTONIC
con clock_gettime
.
Solaris e HP-UX
Solaris ha una propria interfaccia timer ad alta risoluzione gethrtime
che restituisce il valore corrente del timer in nanosecondi. Sebbene siano disponibili le versioni più recenti di Solaris clock_gettime
, è possibile attenersi a gethrtime
se è 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 gethrtime
che è necessario utilizzare come su Solaris.
BeOS
BeOS ha anche una propria interfaccia timer ad alta risoluzione system_time
che 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).
timespec_get
: stackoverflow.com/a/36095407/895245
timespec_get
non è monotonico.
timespec_get
dalla C11
Restituisce fino a nanosecondi, arrotondato alla risoluzione dell'implementazione.
Sembra un furto ANSI da POSIX ' clock_gettime
.
Esempio: a printf
viene 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.c
come:
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_gettime
chiamata di sistema.
Si noti che questo non è un metodo di micro-benchmarking a prova di errore perché:
man clock_gettime
afferma 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?
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.
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.