Hanya menggunakan ANSI C, adakah cara untuk mengukur waktu dengan presisi milidetik atau lebih? Saya sedang menjelajah time.h tetapi saya hanya menemukan fungsi presisi kedua.
Hanya menggunakan ANSI C, adakah cara untuk mengukur waktu dengan presisi milidetik atau lebih? Saya sedang menjelajah time.h tetapi saya hanya menemukan fungsi presisi kedua.
Jawaban:
Tidak ada fungsi ANSI C yang memberikan resolusi waktu lebih baik dari 1 detik, tetapi fungsi POSIX gettimeofday
menyediakan resolusi mikrodetik. Fungsi jam hanya mengukur jumlah waktu yang dihabiskan suatu proses untuk dieksekusi dan tidak akurat pada banyak sistem.
Anda dapat menggunakan fungsi ini seperti ini:
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);
Ini mengembalikan Time elapsed: 1.000870
mesin saya.
timeval::tv_usec
selalu di bawah satu detik, itu berulang. Yaitu untuk mengambil perbedaan waktu lebih besar dari 1 detik, Anda harus:long usec_diff = (e.tv_sec - s.tv_sec)*1000000 + (e.tv_usec - s.tv_usec);
timersub
fungsi. Kita dapat menggunakan tval_result
nilai (tv_sec dan tv_usec) apa adanya.
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
CLOCKS_PER_SEC / 1000
mungkin tidak tepat yang dapat mempengaruhi hasil akhir (meskipun menurut pengalaman saya CLOCKS_PER_SEC
selalu kelipatan 1000). Melakukan (1000 * clock()) / CLOCKS_PER_SEC
kurang rentan terhadap ketidaktepatan pembagian, tetapi di sisi lain lebih rentan terhadap luapan. Hanya beberapa masalah yang perlu dipertimbangkan.
Saya selalu menggunakan fungsi clock_gettime (), mengembalikan waktu dari jam CLOCK_MONOTONIC. Waktu yang dikembalikan adalah jumlah waktu, dalam detik dan nanodetik, sejak beberapa titik yang tidak ditentukan di masa lalu, seperti permulaan sistem pada zaman tersebut.
#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, ...)
dan bahkan ada makro uji fitur _POSIX_MONOTONIC_CLOCK
.
Menerapkan solusi portabel
Seperti yang telah disebutkan di sini bahwa tidak ada solusi ANSI yang tepat dengan ketepatan yang cukup untuk masalah pengukuran waktu, saya ingin menulis tentang cara mendapatkan portabel dan, jika mungkin, solusi pengukuran waktu resolusi tinggi.
Jam monotonik vs stempel waktu
Secara umum ada dua cara pengukuran waktu:
Yang pertama menggunakan penghitung jam monotonik (terkadang disebut penghitung tick) yang menghitung tick dengan frekuensi yang telah ditentukan, jadi jika Anda memiliki nilai tick dan frekuensinya diketahui, Anda dapat dengan mudah mengubah tick ke waktu yang telah berlalu. Sebenarnya tidak ada jaminan bahwa jam monotonik mencerminkan waktu sistem saat ini dengan cara apa pun, ia juga dapat menghitung kutu sejak sistem dinyalakan. Tapi itu menjamin bahwa jam selalu berjalan dengan cara yang meningkat terlepas dari status sistemnya. Biasanya frekuensi terikat ke sumber resolusi tinggi perangkat keras, itulah sebabnya ia memberikan akurasi tinggi (bergantung pada perangkat keras, tetapi sebagian besar perangkat keras modern tidak memiliki masalah dengan sumber jam resolusi tinggi).
Cara kedua memberikan nilai waktu (tanggal) berdasarkan nilai jam sistem saat ini. Ini mungkin juga memiliki resolusi tinggi, tetapi memiliki satu kelemahan utama: nilai waktu semacam ini dapat dipengaruhi oleh penyesuaian waktu sistem yang berbeda, yaitu perubahan zona waktu, perubahan waktu musim panas (DST), pembaruan server NTP, hibernasi sistem, dan sebagainya. di. Dalam beberapa situasi, Anda bisa mendapatkan nilai waktu berlalu yang negatif yang dapat menyebabkan perilaku yang tidak ditentukan. Sebenarnya sumber waktu semacam ini kurang dapat diandalkan dibandingkan yang pertama.
Jadi aturan pertama dalam pengukuran interval waktu adalah menggunakan jam monotonik jika memungkinkan. Biasanya memiliki presisi tinggi, dan desainnya dapat diandalkan.
Strategi fallback
Saat mengimplementasikan solusi portabel, ada baiknya untuk mempertimbangkan strategi fallback: gunakan jam monotonik jika tersedia dan fallback ke pendekatan stempel waktu jika tidak ada jam monotonik dalam sistem.
Windows
Ada artikel bagus berjudul Memperoleh stempel waktu resolusi tinggi di MSDN tentang pengukuran waktu di Windows yang menjelaskan semua detail yang mungkin perlu Anda ketahui tentang dukungan perangkat lunak dan perangkat keras. Untuk mendapatkan cap waktu presisi tinggi di Windows, Anda harus:
kueri frekuensi pengatur waktu (ticks per second) dengan QueryPerformanceFrequency :
LARGE_INTEGER tcounter;
LARGE_INTEGER freq;
if (QueryPerformanceFrequency (&tcounter) != 0)
freq = tcounter.QuadPart;
Frekuensi pengatur waktu ditetapkan pada boot sistem sehingga Anda hanya perlu mendapatkannya sekali.
menanyakan nilai ticks saat ini dengan QueryPerformanceCounter :
LARGE_INTEGER tcounter;
LARGE_INTEGER tick_value;
if (QueryPerformanceCounter (&tcounter) != 0)
tick_value = tcounter.QuadPart;
skala tanda ke waktu yang telah berlalu, yaitu ke mikrodetik:
LARGE_INTEGER usecs = (tick_value - prev_tick_value) / (freq / 1000000);
Menurut Microsoft, Anda seharusnya tidak mengalami masalah dengan pendekatan ini pada Windows XP dan versi yang lebih baru dalam banyak kasus. Tetapi Anda juga dapat menggunakan dua solusi fallback di Windows:
GetTickCount
, tetapi tersedia mulai dari Windows Vista dan yang lebih baru.OS X (macOS)
OS X (macOS) memiliki satuan waktu absolut Mach sendiri yang mewakili jam monotonik. Cara terbaik untuk memulai adalah artikel Apple Q&A Teknis QA1398: Mach Absolute Time Units yang menjelaskan (dengan contoh kode) bagaimana menggunakan API khusus Mach untuk mendapatkan tanda centang monoton. Ada juga pertanyaan lokal tentang itu yang disebut clock_gettime alternatif di Mac OS X yang pada akhirnya mungkin membuat Anda sedikit bingung apa yang harus dilakukan dengan kemungkinan overflow nilai karena frekuensi penghitung digunakan dalam bentuk pembilang dan penyebut. Jadi, contoh singkat bagaimana mendapatkan waktu yang berlalu:
dapatkan pembilang dan penyebut frekuensi 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;
}
}
Anda hanya perlu melakukannya sekali.
menanyakan nilai tick saat ini dengan mach_absolute_time
:
uint64_t tick_value = mach_absolute_time ();
menskalakan tick ke waktu yang telah berlalu, yaitu ke mikrodetik, menggunakan pembilang dan penyebut yang ditanyakan sebelumnya:
uint64_t value_diff = tick_value - prev_tick_value;
/* To prevent overflow */
value_diff /= 1000;
value_diff *= freq_num;
value_diff /= freq_denom;
Ide utama untuk mencegah overflow adalah dengan menurunkan tick ke akurasi yang diinginkan sebelum menggunakan pembilang dan penyebut. Karena resolusi pengatur waktu awal dalam nanodetik, kami membaginya 1000
untuk mendapatkan mikrodetik. Anda dapat menemukan pendekatan yang sama yang digunakan di time_mac.c Chromium . Jika Anda benar-benar membutuhkan akurasi nanodetik, pertimbangkan untuk membaca bagian Bagaimana cara menggunakan mach_absolute_time tanpa meluap? .
Linux dan UNIX
The clock_gettime
panggilan adalah cara Anda terbaik pada setiap sistem yang ramah POSIX. Itu dapat meminta waktu dari sumber jam yang berbeda, dan yang kita butuhkan adalah CLOCK_MONOTONIC
. Tidak semua sistem clock_gettime
mendukung CLOCK_MONOTONIC
, jadi hal pertama yang perlu Anda lakukan adalah memeriksa ketersediaannya:
_POSIX_MONOTONIC_CLOCK
didefinisikan ke nilai >= 0
itu berarti CLOCK_MONOTONIC
tersedia;jika _POSIX_MONOTONIC_CLOCK
didefinisikan untuk 0
itu berarti Anda juga harus memeriksa apakah itu berfungsi saat runtime, saya sarankan untuk menggunakan sysconf
:
#include <unistd.h>
#ifdef _SC_MONOTONIC_CLOCK
if (sysconf (_SC_MONOTONIC_CLOCK) > 0) {
/* A monotonic clock presents */
}
#endif
Penggunaannya clock_gettime
cukup mudah:
dapatkan nilai waktu:
#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;
}
Saya telah memperkecil waktu menjadi mikrodetik di sini.
menghitung selisih dengan nilai waktu sebelumnya diterima dengan cara yang sama:
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;
Strategi fallback terbaik adalah menggunakan gettimeofday
panggilan: ini tidak monotonik, tetapi memberikan resolusi yang cukup baik. Idenya sama dengan dengan clock_gettime
, tetapi untuk mendapatkan nilai waktu Anda harus:
#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;
}
Sekali lagi, nilai waktu diperkecil menjadi mikrodetik.
SGI IRIX
IRIX punya clock_gettime
panggilan, tapi kurang CLOCK_MONOTONIC
. Sebaliknya ia memiliki sumber jam monotoniknya sendiri yang didefinisikan sebagai CLOCK_SGI_CYCLE
yang harus Anda gunakan, bukan CLOCK_MONOTONIC
dengan clock_gettime
.
Solaris dan HP-UX
Solaris memiliki antarmuka gethrtime
pengatur waktu resolusi tinggi yang mengembalikan nilai pengatur waktu saat ini dalam nanodetik. Meskipun versi Solaris yang lebih baru mungkin memiliki clock_gettime
, Anda dapat tetap menggunakannya gethrtime
jika Anda perlu mendukung versi Solaris yang lama.
Penggunaannya sederhana:
#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 tidak memiliki kekurangan clock_gettime
, tetapi mendukung gethrtime
yang harus Anda gunakan dengan cara yang sama seperti pada Solaris.
BeOS
BeOS juga memiliki antarmuka system_time
pengatur waktu resolusi tinggi yang mengembalikan jumlah mikrodetik yang telah berlalu sejak komputer di-boot.
Contoh penggunaan:
#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 memiliki API sendiri untuk mengambil stempel waktu presisi tinggi:
query frekuensi timer (ticks per unit) dengan DosTmrQueryFreq
(untuk compiler GCC):
#define INCL_DOSPROFILE
#define INCL_DOSERRORS
#include <os2.h>
#include <stdint.h>
ULONG freq;
DosTmrQueryFreq (&freq);
menanyakan nilai ticks saat ini dengan 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;
}
skala tanda ke waktu yang telah berlalu, yaitu ke mikrodetik:
uint64_t usecs = (prev_timestamp - timestamp) / (freq / 1000000);
Contoh implementasi
Anda dapat melihat pustaka plibsys yang mengimplementasikan semua strategi yang dijelaskan di atas (lihat ptimeprofiler * .c untuk detailnya).
timespec_get
: stackoverflow.com/a/36095407/895245
timespec_get
tidak monotonik.
timespec_get
dari C11
Mengembalikan hingga nanodetik, dibulatkan ke resolusi penerapan.
Sepertinya penipuan ANSI dari POSIX ' clock_gettime
.
Contoh: a printf
dilakukan setiap 100ms di 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;
}
The C11 N1570 standar rancangan 7.27.2.5 "Fungsi timespec_get mengatakan":
Jika basis adalah TIME_UTC, anggota tv_sec disetel ke jumlah detik sejak implementasi menentukan epoch, dipotong ke nilai keseluruhan dan anggota tv_nsec disetel ke jumlah integral nanodetik, dibulatkan ke resolusi jam sistem. (321)
321) Meskipun objek struct timespec mendeskripsikan waktu dengan resolusi nanodetik, resolusi yang tersedia bergantung pada sistem dan bahkan mungkin lebih besar dari 1 detik.
C ++ 11 juga mendapat std::chrono::high_resolution_clock
: C ++ Cross-Platform High-Resolution Timer
glibc 2.21
Dapat ditemukan di bawah sysdeps/posix/timespec_get.c
sebagai:
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;
}
sangat jelas:
hanya TIME_UTC
saat ini didukung
itu meneruskan ke __clock_gettime (CLOCK_REALTIME, ts)
, yang merupakan API POSIX: http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html
Linux x86-64 memiliki file clock_gettime
panggilan sistem.
Perhatikan bahwa ini bukan metode pembandingan mikro yang terbukti gagal karena:
man clock_gettime
mengatakan bahwa ukuran ini mungkin memiliki diskontinuitas jika Anda mengubah beberapa pengaturan waktu sistem saat program Anda berjalan. Ini harus menjadi peristiwa yang jarang terjadi, dan Anda mungkin bisa mengabaikannya.
ini mengukur waktu dinding, jadi jika penjadwal memutuskan untuk melupakan tugas Anda, tugas itu akan tampak berjalan lebih lama.
Untuk alasan itu getrusage()
mungkin alat pembandingan POSIX yang lebih baik, meskipun presisi maksimum mikrodetiknya lebih rendah.
Informasi lebih lanjut di: Mengukur waktu di Linux - waktu vs jam vs getrusage vs clock_gettime vs gettimeofday vs timespec_get?
Presisi terbaik yang mungkin Anda peroleh adalah melalui penggunaan instruksi "rdtsc" hanya x86, yang dapat memberikan resolusi level-jam (tentu saja tidak harus memperhitungkan biaya panggilan rdtsc itu sendiri, yang dapat diukur dengan mudah di aplikasi startup).
Tangkapan utama di sini adalah mengukur jumlah jam per detik, yang seharusnya tidak terlalu sulit.
Jawaban yang diterima cukup bagus, tetapi solusi saya lebih sederhana, saya hanya menguji di Linux, gunakan gcc (Ubuntu 7.2.0-8ubuntu3.2) 7.2.0.
Alse use gettimeofday
, the tv_sec
adalah bagian dari detik, dan tv_usec
adalah mikrodetik , bukan milidetik .
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;
}
Ini mencetak:
1522139691342
1522139692342
, tepat satu detik.