ANSI C を使用して時間をミリ秒単位で測定するにはどうすればよいですか?
-
21-08-2019 - |
質問
ANSI C のみを使用して、ミリ秒以上の精度で時間を測定する方法はありますか?time.h を参照していましたが、秒精度の関数しか見つかりませんでした。
解決
他のヒント
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
私はいつもCLOCK_MONOTONICクロックから時刻を返す、にclock_gettime()関数を使用します。返された時間は、エポックのシステム起動時など、過去のいくつかの不特定のポイント、以来、秒およびナノ秒単位で、時間の量である。
#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 ソリューションが存在しないことはここですでに述べましたが、ポータブルで、可能であれば高解像度の時間測定ソリューションを入手する方法について書きたいと思います。
単調クロック vs.タイムスタンプ
一般に、時間の測定には次の 2 つの方法があります。
- 単調な時計。
- 現在の(日付)タイムスタンプ。
1 つ目は、事前定義された周波数でティックをカウントする単調クロック カウンター (ティック カウンターと呼ばれることもあります) を使用するため、ティック値があり、周波数が既知であれば、ティックを経過時間に簡単に変換できます。実際には、単調クロックが現在のシステム時刻を何らかの形で反映していることは保証されておらず、システム起動以降のティックもカウントする可能性があります。ただし、システムの状態に関係なく、クロックが常に増加するように動作することが保証されます。通常、周波数はハードウェアの高解像度クロック ソースにバインドされているため、高精度が得られます (ハードウェアによって異なりますが、最新のハードウェアのほとんどは高解像度クロック ソースで問題ありません)。
2 番目の方法では、現在のシステム クロック値に基づいて (日付) 時刻値を提供します。解像度も高いかもしれませんが、大きな欠点が 1 つあります。この種の時間値は、さまざまなシステム時間調整の影響を受ける可能性があります。タイムゾーンの変更、夏時間 (DST) の変更、NTP サーバーの更新、システムの休止状態など。状況によっては、負の経過時間値が取得され、未定義の動作が発生する可能性があります。実際、この種の時間ソースは最初のものよりも信頼性が低くなります。
したがって、時間間隔測定における最初のルールは、可能であれば単調クロックを使用することです。通常は精度が高く、設計上信頼性が高くなります。
フォールバック戦略
ポータブル ソリューションを実装する場合は、フォールバック戦略を検討する価値があります。利用可能な場合は単調クロックを使用し、システムに単調クロックがない場合はタイムスタンプアプローチにフォールバックします。
ウィンドウズ
という素晴らしい記事があります 高解像度のタイムスタンプの取得 MSDN の Windows での時間測定については、ソフトウェアとハードウェアのサポートについて知っておく必要があるすべての詳細が説明されています。Windows で高精度のタイムスタンプを取得するには、次のことを行う必要があります。
タイマーの頻度 (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 の記事から始めるのが最善の方法です 技術的なQ&A QA1398:マッハ絶対時間単位 これは、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; } }
それを行う必要があるのは 1 回だけです。
現在のティック値をクエリする
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
アイリックス 持っています clock_gettime
電話しますが、不足しています CLOCK_MONOTONIC
. 。代わりに、次のように定義された独自の単調クロック ソースがあります。 CLOCK_SGI_CYCLE
代わりにどれを使用する必要がありますか CLOCK_MONOTONIC
と clock_gettime
.
Solaris と HP-UX
Solaris には独自の高解像度タイマー インターフェイスがあります gethrtime
現在のタイマー値をナノ秒単位で返します。Solaris の新しいバージョンでは、 clock_gettime
, 、あなたは固執することができます gethrtime
古い Solaris バージョンをサポートする必要がある場合。
使い方は簡単です:
#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に欠けているもの clock_gettime
, 、しかしそれはサポートします gethrtime
Solaris と同じ方法で使用する必要があります。
BeOS
BeOS 独自の高解像度タイマーインターフェイスもあります system_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);
実装例
をご覧ください。 プリブシス 上記のすべての戦略を実装するライブラリ (詳細については、ptimeprofiler*.c を参照)。
timespec_get
C11から
実装の解像度に丸めてナノ秒まで返します。
POSIX からの ANSI のパクリのように見えます。 clock_gettime
.
例:ある printf
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 関数の内容」:
Baseが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 ベンチマーク ツールの方が優れている可能性があります。詳細については、次をご覧ください。 Linux での時間の測定 - 時間 vs クロック vs getrusage vs Clock_gettime vs gettimeofday vs timespec_get?
あなたはおそらく得ることができる最高の精度は(当然のNEの必見は、アカウントに、RDTSC、自分自身を呼び出すことができるのコストがかかるクロックレベルの分解能を提供できるのx86のみ「RDTSC」命令の使用によるものです)アプリケーションの起動時に容易に測定ます。
ここでの主な漁獲量があまりにも難しいことではありません秒あたりのクロック数を、測定されます。
受け入れ答えは良いですenough.But私の解決策は、Linuxでよりsimple.I単なるテストで、GCC(Ubuntuの7.2.0-8ubuntu3.2)7.2.0を使用します。
は、使用gettimeofday
Alse、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
、正確に二ます。
Windows環境下でます:
SYSTEMTIME t;
GetLocalTime(&t);
swprintf_s(buff, L"[%02d:%02d:%02d:%d]\t", t.wHour, t.wMinute, t.wSecond, t.wMilliseconds);