Używając tylko ANSI C, czy istnieje sposób mierzenia czasu z dokładnością do milisekund lub większą? Przeglądałem czas.h, ale znalazłem tylko funkcje drugiej precyzji.
Używając tylko ANSI C, czy istnieje sposób mierzenia czasu z dokładnością do milisekund lub większą? Przeglądałem czas.h, ale znalazłem tylko funkcje drugiej precyzji.
Odpowiedzi:
Nie ma funkcji ANSI C, która zapewnia rozdzielczość lepszą niż 1 sekunda, ale funkcja POSIX gettimeofday
zapewnia rozdzielczość mikrosekundową. Funkcja zegara mierzy tylko ilość czasu, jaką proces spędził na wykonywaniu i nie jest dokładna w wielu systemach.
Możesz użyć tej funkcji w następujący sposób:
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);
To powraca Time elapsed: 1.000870
na moim komputerze.
timeval::tv_usec
jest zawsze poniżej jednej sekundy, jest zapętlony. Tj. Aby wziąć różnice czasu większe niż 1 sekundę, należy:long usec_diff = (e.tv_sec - s.tv_sec)*1000000 + (e.tv_usec - s.tv_usec);
timersub
funkcji. Możemy użyć tval_result
wartości (tv_sec i tv_usec) bez zmian.
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
CLOCKS_PER_SEC / 1000
może być niedokładne, co może wpłynąć na wynik końcowy (chociaż z mojego doświadczenia CLOCKS_PER_SEC
zawsze wynikało wielokrotność 1000). Robi (1000 * clock()) / CLOCKS_PER_SEC
to mniej podatne na podział niedokładność, ale z drugiej strony jest bardziej podatny na przepełnienie. Tylko kilka kwestii do rozważenia.
Zawsze używam funkcji clock_gettime (), zwracając czas z zegara CLOCK_MONOTONIC. Zwrócony czas to ilość czasu wyrażona w sekundach i nanosekundach od jakiegoś nieokreślonego punktu w przeszłości, takiego jak uruchomienie systemu w danej epoce.
#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, ...)
i jest nawet makro testowania funkcji _POSIX_MONOTONIC_CLOCK
.
Wdrożenie rozwiązania przenośnego
Jak już tutaj wspomniano, nie ma odpowiedniego rozwiązania ANSI o wystarczającej precyzji dla problemu pomiaru czasu, chcę napisać o sposobach uzyskania przenośnego i, jeśli to możliwe, rozwiązania do pomiaru czasu o wysokiej rozdzielczości.
Zegar monotoniczny a znaczniki czasu
Ogólnie rzecz biorąc, istnieją dwa sposoby pomiaru czasu:
Pierwsza używa monotonicznego licznika zegara (czasami nazywanego licznikiem tików), który zlicza tyknięcia z predefiniowaną częstotliwością, więc jeśli masz wartość tyknięć i częstotliwość jest znana, możesz łatwo przekonwertować tyknięcia na upływający czas. W rzeczywistości nie ma gwarancji, że zegar monotoniczny w jakikolwiek sposób odzwierciedla bieżący czas systemowy, może również liczyć tyknięcia od momentu uruchomienia systemu. Ale to gwarantuje, że zegar jest zawsze uruchamiany w sposób rosnący, niezależnie od stanu systemu. Zwykle częstotliwość jest związana ze sprzętowym źródłem o wysokiej rozdzielczości, dlatego zapewnia wysoką dokładność (zależy od sprzętu, ale większość współczesnego sprzętu nie ma problemów ze źródłami zegara o wysokiej rozdzielczości).
Drugi sposób zapewnia (datę) wartość czasu na podstawie bieżącej wartości zegara systemowego. Może mieć również wysoką rozdzielczość, ale ma jedną poważną wadę: na ten rodzaj wartości czasu mogą wpływać różne ustawienia czasu systemu, tj. Zmiana strefy czasowej, zmiana czasu letniego (DST), aktualizacja serwera NTP, hibernacja systemu i tak dalej. na. W niektórych okolicznościach można uzyskać ujemną wartość czasu, który upłynął, co może prowadzić do nieokreślonego zachowania. W rzeczywistości tego rodzaju źródło czasu jest mniej niezawodne niż pierwsze.
Tak więc pierwszą zasadą pomiaru odstępów czasu jest użycie zegara monotonicznego, jeśli to możliwe. Zwykle ma wysoką precyzję i jest niezawodny z założenia.
Strategia rezerwowa
Wdrażając rozwiązanie przenośne, warto rozważyć strategię awaryjną: użyj zegara monotonicznego, jeśli jest dostępny, i podejścia rezerwowego do znaczników czasu, jeśli w systemie nie ma zegara monotonicznego.
Windows
Jest świetny artykuł zatytułowany Uzyskiwanie znaczników czasu w wysokiej rozdzielczości w witrynie MSDN na temat pomiaru czasu w systemie Windows, w którym opisano wszystkie szczegóły, które mogą być potrzebne do obsługi oprogramowania i sprzętu. Aby uzyskać precyzyjny znacznik czasu w systemie Windows, należy:
zapytaj o częstotliwość zegara ( takty na sekundę) za pomocą QueryPerformanceFrequency :
LARGE_INTEGER tcounter;
LARGE_INTEGER freq;
if (QueryPerformanceFrequency (&tcounter) != 0)
freq = tcounter.QuadPart;
Częstotliwość timera jest ustalana podczas rozruchu systemu, więc musisz ją pobrać tylko raz.
Zapytaj o bieżącą wartość ticków za pomocą QueryPerformanceCounter :
LARGE_INTEGER tcounter;
LARGE_INTEGER tick_value;
if (QueryPerformanceCounter (&tcounter) != 0)
tick_value = tcounter.QuadPart;
przeskaluj znaczniki do upływającego czasu, tj. do mikrosekund:
LARGE_INTEGER usecs = (tick_value - prev_tick_value) / (freq / 1000000);
Według Microsoftu w większości przypadków nie powinno być żadnych problemów z takim podejściem na Windows XP i nowszych wersjach. Ale możesz też użyć dwóch rozwiązań awaryjnych w systemie Windows:
GetTickCount
, ale jest dostępna począwszy od systemu Windows Vista i nowszych.OS X (macOS)
OS X (macOS) ma własne jednostki czasu bezwzględnego Macha, które reprezentują zegar monotoniczny. Najlepszym sposobem na rozpoczęcie jest artykuł firmy Apple Technical Q&A QA1398: Mach Absolute Time Units, w którym opisano (z przykładami kodu), jak używać interfejsu API specyficznego dla Macha, aby uzyskać monotoniczne tiki. Istnieje również lokalne pytanie na ten temat, zwane alternatywą clock_gettime w systemie Mac OS X, które na końcu może sprawić, że będziesz nieco zdezorientowany, co zrobić z możliwym przepełnieniem wartości, ponieważ częstotliwość licznika jest używana w postaci licznika i mianownika. A więc krótki przykład, jak obliczyć upływ czasu:
pobierz licznik i mianownik częstotliwości zegara:
#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;
}
}
Musisz to zrobić tylko raz.
zapytaj o aktualną wartość ticka za pomocą mach_absolute_time
:
uint64_t tick_value = mach_absolute_time ();
przeskaluj takty do upływającego czasu, tj. do mikrosekund, używając wcześniej zadanego licznika i mianownika:
uint64_t value_diff = tick_value - prev_tick_value;
/* To prevent overflow */
value_diff /= 1000;
value_diff *= freq_num;
value_diff /= freq_denom;
Główną ideą zapobiegania przepełnieniu jest zmniejszenie taktów do pożądanej dokładności przed użyciem licznika i mianownika. Ponieważ początkowa rozdzielczość timera jest w nanosekundach, dzielimy ją przez, 1000
aby uzyskać mikrosekundy. Możesz znaleźć to samo podejście, które zastosowano w time_mac.c Chromium . Jeśli naprawdę potrzebujesz dokładności w nanosekundach, rozważ przeczytanie Jak mogę używać mach_absolute_time bez przepełnienia? .
Linux i UNIX
clock_gettime
Rozmowa jest najlepszym sposobem na każdym systemie POSIX w obsłudze. Może sprawdzać czas z różnych źródeł zegara, a ten, którego potrzebujemy, to CLOCK_MONOTONIC
. Nie wszystkie systemy, które mają clock_gettime
wsparcie CLOCK_MONOTONIC
, więc pierwszą rzeczą, którą musisz zrobić, jest sprawdzenie jego dostępności:
_POSIX_MONOTONIC_CLOCK
jest zdefiniowany do wartości, >= 0
to znaczy, że CLOCK_MONOTONIC
jest dostępny;jeśli _POSIX_MONOTONIC_CLOCK
jest zdefiniowany 0
to znaczy że należy dodatkowo sprawdzić czy działa w runtime, proponuję użyć sysconf
:
#include <unistd.h>
#ifdef _SC_MONOTONIC_CLOCK
if (sysconf (_SC_MONOTONIC_CLOCK) > 0) {
/* A monotonic clock presents */
}
#endif
Użycie clock_gettime
jest dość proste:
pobierz wartość czasu:
#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;
}
Skróciłem czas do mikrosekund.
obliczyć różnicę w stosunku do poprzedniej wartości czasu otrzymanej w ten sam sposób:
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;
Najlepszą strategią rezerwową jest użycie gettimeofday
wywołania: nie jest on monotoniczny, ale zapewnia całkiem niezłą rozdzielczość. Pomysł jest taki sam jak w przypadku clock_gettime
, ale aby uzyskać wartość czasu, należy:
#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;
}
Ponownie wartość czasu jest zmniejszana do mikrosekund.
SGI IRIX
IRIX ma clock_gettime
wezwanie, ale brakuje CLOCK_MONOTONIC
. Zamiast tego ma własne monotoniczne źródło zegara zdefiniowane jako, CLOCK_SGI_CYCLE
którego należy używać zamiast CLOCK_MONOTONIC
with clock_gettime
.
Solaris i HP-UX
Solaris ma własny interfejs timera o wysokiej rozdzielczości, gethrtime
który zwraca bieżącą wartość timera w nanosekundach. Chociaż nowsze wersje Solaris mogą mieć clock_gettime
, możesz trzymać się, gethrtime
jeśli potrzebujesz obsługiwać stare wersje Solaris.
Użycie jest proste:
#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;
}
Brakuje HP-UX clock_gettime
, ale obsługuje, z gethrtime
którego należy korzystać w taki sam sposób, jak w Solarisie.
BeOS
BeOS ma również własny interfejs timera o wysokiej rozdzielczości, system_time
który zwraca liczbę mikrosekund, które upłynęły od momentu uruchomienia komputera.
Przykładowe użycie:
#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 ma własny interfejs API do pobierania precyzyjnych znaczników czasu:
Zapytaj o częstotliwość timera (takty na jednostkę) z DosTmrQueryFreq
(dla kompilatora GCC):
#define INCL_DOSPROFILE
#define INCL_DOSERRORS
#include <os2.h>
#include <stdint.h>
ULONG freq;
DosTmrQueryFreq (&freq);
zapytaj o bieżącą wartość ticków za pomocą 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;
}
przeskaluj znaczniki do upływającego czasu, tj. do mikrosekund:
uint64_t usecs = (prev_timestamp - timestamp) / (freq / 1000000);
Przykładowa realizacja
Możesz rzucić okiem na bibliotekę plibsys, która implementuje wszystkie opisane powyżej strategie (szczegóły w ptimeprofiler * .c).
timespec_get
: stackoverflow.com/a/36095407/895245
timespec_get
nie jest monotonna.
timespec_get
z C11
Zwraca do nanosekund, w zaokrągleniu do rozdzielczości implementacji.
Wygląda na zdzierstwo ANSI z POSIX clock_gettime
.
Przykład: a printf
jest wykonywane co 100 ms na 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;
}
Wersja standardowa C11 N1570 7.27.2.5 "Funkcja timespec_get mówi":
Jeśli podstawa ma wartość TIME_UTC, element członkowski tv_sec jest ustawiany na liczbę sekund od zdefiniowanej przez implementację epoki, obcięty do pełnej wartości, a element członkowski tv_nsec jest ustawiany na całkowitą liczbę nanosekund, zaokrągloną do rozdzielczości zegara systemowego. (321)
321) Chociaż obiekt struct timespec opisuje czasy z rozdzielczością nanosekund, dostępna rozdzielczość jest zależna od systemu i może być nawet większa niż 1 sekunda.
C ++ 11 ma również std::chrono::high_resolution_clock
: C ++ Cross-Platform High-Resolution Timer
implementacja glibc 2.21
Można znaleźć sysdeps/posix/timespec_get.c
jako:
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;
}
tak wyraźnie:
tylko TIME_UTC
jest obecnie obsługiwany
przekazuje do __clock_gettime (CLOCK_REALTIME, ts)
, który jest interfejsem API POSIX: http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html
Linux x86-64 ma clock_gettime
wywołanie systemowe.
Należy pamiętać, że nie jest to niezawodna metoda mikro-benchmarkingu, ponieważ:
man clock_gettime
mówi, że miara ta może mieć nieciągłości, jeśli zmienisz ustawienia czasu systemowego podczas działania programu. Powinno to być oczywiście rzadkie zdarzenie i możesz je zignorować.
mierzy czas na ścianie, więc jeśli planista zdecyduje się zapomnieć o zadaniu, będzie ono działać dłużej.
Z tych powodów getrusage()
może być lepszym lepszym narzędziem do testów porównawczych POSIX, pomimo mniejszej maksymalnej precyzji w mikrosekundach.
Więcej informacji: Mierz czas w Linuksie - czas vs zegar vs getrusage vs clock_gettime vs gettimeofday vs timespec_get?
Najlepsza precyzja, jaką można uzyskać, to użycie instrukcji "rdtsc" tylko dla x86, która może zapewnić rozdzielczość na poziomie zegara (nie musi oczywiście uwzględniać kosztu samego wywołania rdtsc, który można łatwo zmierzyć uruchomienie aplikacji).
Głównym haczykiem jest tutaj pomiar liczby zegarów na sekundę, co nie powinno być zbyt trudne.
Przyjęta odpowiedź jest wystarczająca, ale moje rozwiązanie jest prostsze: po prostu testuję w Linuksie, używam gcc (Ubuntu 7.2.0-8ubuntu3.2) 7.2.0.
Poza tym gettimeofday
, tv_sec
jest częścią sekundy, a tv_usec
jest to mikrosekundy , a nie milisekundy .
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;
}
To drukuje:
1522139691342
1522139692342
, dokładnie sekundę.