ANSI Cのみを使用して、ミリ秒以上の精度で時間を測定する方法はありますか? time.h を閲覧していましたが、2 番目の精度の関数しか見つかりませんでした。
8 に答える
1 秒より優れた時間分解能を提供する ANSI C 関数はありませんが、POSIX 関数gettimeofday
はマイクロ秒の分解能を提供します。時計機能は、プロセスの実行に費やされた時間を測定するだけであり、多くのシステムでは正確ではありません。
この関数は次のように使用できます。
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);
Time elapsed: 1.000870
これは私のマシンに戻ります。
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
私は常に clock_gettime() 関数を使用して、CLOCK_MONOTONIC クロックから時間を返します。返される時間は、エポックのシステム起動など、過去の特定されていない時点からの時間 (秒およびナノ秒) です。
#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);
}
ポータブル ソリューションの実装
時間測定の問題に対して十分な精度を備えた適切な ANSI ソリューションがないことは既にここで述べたので、ポータブルで、可能であれば高解像度の時間測定ソリューションを入手する方法について書きたいと思います。
モノトニック クロックとタイム スタンプ
一般的に言えば、時間の測定には次の 2 つの方法があります。
- 単調時計;
- 現在の (日付) タイムスタンプ。
最初のものは、定義済みの頻度でティックをカウントするモノトニック クロック カウンター (ティック カウンターと呼ばれることもあります) を使用するため、ティック値があり、頻度がわかっている場合は、ティックを経過時間に簡単に変換できます。単調クロックが現在のシステム時間を何らかの方法で反映することは実際には保証されていません。システムの起動以降のティックをカウントする場合もあります。ただし、システムの状態に関係なく、クロックが常に増加する方法で実行されることが保証されます。通常、周波数はハードウェアの高解像度ソースにバインドされているため、高精度が提供されます (ハードウェアによって異なりますが、最新のハードウェアのほとんどは高解像度クロック ソースに問題はありません)。
2 番目の方法は、現在のシステム クロック値に基づいて (日付) 時刻の値を提供します。解像度も高い場合がありますが、大きな欠点が 1 つあります。この種の時間値は、タイム ゾーンの変更、夏時間 (DST) の変更、NTP サーバーの更新、システムの休止状態など、さまざまなシステム時間調整の影響を受ける可能性があります。の上。状況によっては、未定義の動作につながる可能性のある負の経過時間値を取得できます。実際、この種のタイム ソースは最初のものよりも信頼性が低くなります。
したがって、時間間隔測定の最初のルールは、可能であれば単調クロックを使用することです。通常は精度が高く、設計上信頼性があります。
フォールバック戦略
ポータブル ソリューションを実装する場合は、フォールバック戦略を検討する価値があります。利用可能な場合はモノトニック クロックを使用し、システムにモノトニック クロックがない場合はタイムスタンプ アプローチにフォールバックします。
ウィンドウズ
Windows での時間測定に関する MSDNの Acquiring high resolution time stampsという優れた記事があり、ソフトウェアとハードウェアのサポートについて知っておく必要があるすべての詳細が説明されています。Windows で高精度のタイム スタンプを取得するには、次のことを行う必要があります。
QueryPerformanceFrequencyでタイマー頻度 (1 秒あたりのティック数) を照会します。
LARGE_INTEGER tcounter; LARGE_INTEGER freq; if (QueryPerformanceFrequency (&tcounter) != 0) freq = tcounter.QuadPart;
タイマーの頻度はシステムの起動時に固定されるため、取得する必要があるのは 1 回だけです。
QueryPerformanceCounterで現在のティック値を照会します。
LARGE_INTEGER tcounter; LARGE_INTEGER tick_value; if (QueryPerformanceCounter (&tcounter) != 0) tick_value = tcounter.QuadPart;
ティックを経過時間、つまりマイクロ秒にスケーリングします。
LARGE_INTEGER usecs = (tick_value - prev_tick_value) / (freq / 1000000);
Microsoft によると、ほとんどの場合、Windows XP 以降のバージョンでは、このアプローチで問題は発生しないはずです。ただし、Windows では次の 2 つのフォールバック ソリューションを使用することもできます。
- GetTickCountは、システムが起動してから経過したミリ秒数を提供します。49.7 日ごとにラップするため、より長い間隔を測定する場合は注意してください。
- GetTickCount64は の 64 ビット バージョンですが
GetTickCount
、Windows Vista 以降で使用できます。
OS X (macOS)
OS X (macOS) には、単調時計を表す独自のマッハ絶対時間単位があります。開始する最良の方法は、Apple の記事Technical Q&A QA1398: Mach Absolute Time Unitsで、Mach 固有の API を使用して単調なティックを取得する方法を (コード例とともに) 説明しています。これについては、Mac OS X の clock_gettime 代替と呼ばれるローカルな質問もあります。カウンタ周波数が分子と分母の形式で使用されるため、最終的には値オーバーフローの可能性をどうするか少し混乱する可能性があります。したがって、経過時間を取得する方法の簡単な例:
クロック周波数の分子と分母を取得します。
#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; } }
これは一度だけ行う必要があります。
現在のティック値を次のようにクエリします
mach_absolute_time
。uint64_t tick_value = mach_absolute_time ();
以前にクエリされた分子と分母を使用して、ティックを経過時間、つまりマイクロ秒にスケーリングします。
uint64_t value_diff = tick_value - prev_tick_value; /* To prevent overflow */ value_diff /= 1000; value_diff *= freq_num; value_diff /= freq_denom;
オーバーフローを防ぐための主なアイデアは、分子と分母を使用する前に目盛りを目的の精度に縮小することです。初期のタイマーの分解能はナノ秒単位であるため、それを で割って
1000
マイクロ秒を取得します。Chromium のtime_mac.cで使用されているのと同じアプローチを見つけることができます。本当にナノ秒の精度が必要な場合は、オーバーフローせずに mach_absolute_time を使用するにはどうすればよいですか? を読むことを検討してください。.
Linux と UNIX
このclock_gettime
呼び出しは、POSIX に適したシステムでは最善の方法です。さまざまなクロック ソースから時間をクエリできます。必要なのはCLOCK_MONOTONIC
. すべてのシステムがclock_gettime
サポートされているわけではないCLOCK_MONOTONIC
ため、最初に行う必要があるのは、その可用性を確認することです。
_POSIX_MONOTONIC_CLOCK
が値に定義されている場合、それは利用可能>= 0
であることを意味しCLOCK_MONOTONIC
ます。_POSIX_MONOTONIC_CLOCK
が定義されている場合は0
、実行時に機能するかどうかをさらに確認する必要があることを意味します。次を使用することをお勧めしますsysconf
。#include <unistd.h> #ifdef _SC_MONOTONIC_CLOCK if (sysconf (_SC_MONOTONIC_CLOCK) > 0) { /* A monotonic clock presents */ } #endif
- それ以外の場合、単調クロックはサポートされず、フォールバック戦略を使用する必要があります (以下を参照)。
の使用法clock_gettime
は非常に簡単です。
時間値を取得します。
#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; }
ここでは時間をマイクロ秒に縮小しました。
同じ方法で受け取った前回の時間値との差を計算します。
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;
最良のフォールバック戦略は、gettimeofday
呼び出しを使用することです。これは単調ではありませんが、非常に優れた解決策を提供します。考え方は と同じclock_gettime
ですが、時間値を取得するには:
#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;
}
ここでも、時間値はマイクロ秒に縮小されます。
SGI IRIX
IRIXにはclock_gettime
呼び出しがありますが、欠けていCLOCK_MONOTONIC
ます。代わりに、独自のモノトニック クロック ソースが定義されており、 withCLOCK_SGI_CYCLE
の代わりに使用する必要があります。CLOCK_MONOTONIC
clock_gettime
Solaris と HP-UX
gethrtime
Solaris には、現在のタイマー値をナノ秒単位で返す独自の高解像度タイマー インターフェイスがあります。Solaris の新しいバージョンには があるかもしれませんが、古い Solaris バージョンをサポートする必要がある場合はそのまま使用clock_gettime
できます。gethrtime
使い方は簡単です:
#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 にはがありませんが、Solaris と同じように使用する必要があるclock_gettime
ものをサポートしています。gethrtime
BeOS
BeOSsystem_time
には、コンピューターが起動してから経過したマイクロ秒数を返す独自の高解像度タイマー インターフェイスもあります。
使用例:
#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には、高精度のタイム スタンプを取得する独自の API があります。
DosTmrQueryFreq
(GCC コンパイラの場合)を使用して、タイマーの頻度 (単位ごとのティック数) をクエリします。#define INCL_DOSPROFILE #define INCL_DOSERRORS #include <os2.h> #include <stdint.h> ULONG freq; DosTmrQueryFreq (&freq);
現在のティック値を次のようにクエリします
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; }
ティックを経過時間、つまりマイクロ秒にスケーリングします。
uint64_t usecs = (prev_timestamp - timestamp) / (freq / 1000000);
実装例
上記のすべての戦略を実装するplibsysライブラリを確認できます(詳細については、ptimeprofiler*.c を参照してください)。
timespec_get
C11から
実装の解像度に丸められたナノ秒まで返します。
POSIX' からの ANSI ぼったくりのように見えclock_gettime
ます。
例: aprintf
は Ubuntu 15.10 で 100 ミリ秒ごとに実行されます。
#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;
}
C11 N1570標準ドラフト7.27.2.5 「timespec_get 関数は言う」:
ベースが TIME_UTC の場合、tv_sec メンバーは実装定義のエポックからの秒数に設定され、整数値に切り捨てられ、tv_nsec メンバーはナノ秒の整数に設定され、システム クロックの解像度に丸められます。(321)
321) struct timespec オブジェクトはナノ秒の解像度で時間を記述しますが、利用可能な解像度はシステムに依存し、1 秒を超える場合もあります。
C++11 も取得std::chrono::high_resolution_clock
: C++ クロスプラットフォームの高解像度タイマー
glibc 2.21 実装
次の場所にありますsysdeps/posix/timespec_get.c
。
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;
}
とても明確に:
のみ
TIME_UTC
が現在サポートされています__clock_gettime (CLOCK_REALTIME, ts)
POSIX API であるに転送します: http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.htmlLinux x86-64 には
clock_gettime
システム コールがあります。以下の理由により、これはフェイルプルーフのマイクロベンチマーク方法ではないことに注意してください。
man clock_gettime
は、プログラムの実行中にシステム時間の設定を変更すると、この測定値が不連続になる可能性があると述べています。もちろん、これはまれなイベントであり、無視できる場合があります。これは経過時間を測定するため、スケジューラーがタスクを忘れると判断した場合、タスクはより長く実行されているように見えます。
これらの理由から
getrusage()
、マイクロ秒の最大精度が低いにもかかわらず、より優れた POSIX ベンチマーク ツールである可能性があります。
可能な限り最高の精度は、クロックレベルの解像度を提供できるx86のみの「rdtsc」命令を使用することです(もちろん、rdtsc呼び出し自体のコストを考慮する必要があります。これはで簡単に測定できます。アプリケーションの起動)。
ここでの主な問題は、1秒あたりのクロック数を測定することですが、それほど難しくはありません。
受け入れられた答えは十分です.しかし、私の解決策はもっと簡単です.Linuxでテストし、gcc(Ubuntu 7.2.0-8ubuntu3.2)7.2.0を使用します.
またgettimeofday
、は秒tv_sec
の一部であり、tv_usec
はミリ秒ではなくマイクロ秒です。
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;
}
それは印刷します:
1522139691342
1522139692342
、ちょうど 1 秒。
^
窓の下:
SYSTEMTIME t;
GetLocalTime(&t);
swprintf_s(buff, L"[%02d:%02d:%02d:%d]\t", t.wHour, t.wMinute, t.wSecond, t.wMilliseconds);