ANSI Cのみを使用して、ミリ秒以上の精度で時間を測定する方法はありますか?私はtime.hを閲覧していましたが、2番目の精度の関数しか見つけませんでした。
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);
ポータブルソリューションの実装
ここで、時間測定の問題に対して十分な精度を備えた適切なANSIソリューションがないことを既に述べたので、ポータブルで、可能であれば高解像度の時間測定ソリューションを取得する方法について書きたいと思います。
単調クロックとタイムスタンプ
一般的に、時間測定には2つの方法があります。
1つ目は、単調なクロックカウンター(ティックカウンターと呼ばれることもあります)を使用して、事前定義された頻度でティックをカウントします。したがって、ティック値があり、頻度がわかっている場合は、ティックを経過時間に簡単に変換できます。実際には、単調な時計が現在のシステム時刻を反映することは保証されていません。また、システム起動後のティックをカウントすることもあります。ただし、システムの状態に関係なく、クロックが常に増加するように保証されます。通常、周波数はハードウェアの高解像度ソースにバインドされているため、高精度を提供します(ハードウェアに依存しますが、現代のハードウェアのほとんどは高解像度クロックソースに問題はありません)。
2番目の方法は、現在のシステムクロック値に基づいて(日付)時間値を提供します。高解像度の場合もありますが、1つの大きな欠点があります。この種の時間値は、異なるシステム時間調整、つまりタイムゾーンの変更、夏時間(DST)の変更、NTPサーバーの影響を受ける可能性があります更新、システムの休止状態など。状況によっては、未定義の動作を引き起こす可能性がある負の経過時間値を取得できます。実際、この種のタイムソースは最初のものよりも信頼性が低くなります。
したがって、時間間隔測定の最初のルールは、可能であれば単調クロックを使用することです。通常は高精度であり、設計上信頼性があります。
フォールバック戦略
ポータブルソリューションを実装するときは、フォールバック戦略を検討する価値があります。使用可能な場合は単調クロックを使用し、システムに単調クロックがない場合はタイムスタンプへのフォールバックアプローチを使用します。
Windows
MSDNには、Windowsでの時間測定に関する 高解像度タイムスタンプの取得 という素晴らしい記事があり、ソフトウェアとハードウェアのサポートについて知る必要があるすべての詳細が説明されています。 Windowsで高精度のタイムスタンプを取得するには、以下を行う必要があります。
QueryPerformanceFrequency でタイマーの頻度(1秒あたりのティック数)を照会します。
LARGE_INTEGER tcounter;
LARGE_INTEGER freq;
if (QueryPerformanceFrequency (&tcounter) != 0)
freq = tcounter.QuadPart;
タイマーの頻度はシステムの起動時に固定されるため、一度だけ取得する必要があります。
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
の64ビットバージョンですが、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
がありません。代わりに、CLOCK_SGI_CYCLE
でCLOCK_MONOTONIC
の代わりに使用する必要があるclock_gettime
として定義された独自の単調なクロックソースがあります。
SolarisおよびHP-UX
Solarisには、ナノ秒単位で現在のタイマー値を返す独自の高解像度タイマーインターフェイスgethrtime
があります。 Solarisの新しいバージョンにはclock_gettime
がありますが、古いSolarisバージョンをサポートする必要がある場合は、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には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);
実装例
上記の戦略をすべて実装する plibsys ライブラリを見ることができます(詳細についてはptimeprofiler * .cを参照してください)。
timespec_get
from C11
実装の解像度に丸められた最大ナノ秒を返します。
POSIXのclock_gettime
からのANSIリッピングのように見えます。
例: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++ Cross-Platform High-Resolution Timer
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
のみです
pOSIX APIである__clock_gettime (CLOCK_REALTIME, ts)
に転送します。 http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html
Linux x86-64にはclock_gettime
システムコールがあります。
これは、次の理由により、フェイルプルーフのマイクロベンチマーク方法ではないことに注意してください。
man clock_gettime
は、プログラムの実行中にシステム時間の設定を変更すると、この測定値が不連続になる可能性があることを示しています。もちろん、これはまれなイベントであり、無視してもかまいません。
これはウォール時間を測定するため、スケジューラがタスクを忘れると判断した場合、実行時間が長くなるように見えます。
これらの理由により、getrusage()
は、マイクロ秒の最大精度が低いにもかかわらず、より優れたPOSIXベンチマークツールになる可能性があります。
詳細情報: Linuxでの時間の測定-時間vsクロックvs getrusage vs clock_gettime vs gettimeofday vs timespec_get?
可能な最高の精度は、クロックレベルの解像度を提供できる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秒。