ANSI Cを使用してミリ秒単位で時間を測定する方法


127

ANSI Cのみを使用して、ミリ秒以上の精度で時間を測定する方法はありますか?私はtime.hを参照していましたが、2番目の精度関数しか見つかりませんでした。


4
精度と精度の違いに注意してください。秒単位で時間を取り、1000を掛けることで、ミリ秒の精度で時間を取得できますが、それは無駄です。ms精度関数は必ずしもms精度を備えている必要はありませんが、一般に1s精度よりも優れています。
スティーブジェソップ

2
簡単な答えは「いいえ」です。ANSICは、ミリ秒以上の精度をサポートしていません。より複雑な答えは、広く利用可能なPosix関数の使用を許可したとしても、何をしようとしているのかによって異なります-率直に言って、領域全体が悪夢です。「メジャー」という用語を使用するので、「ウォールクロック」時間ではなく間隔に関心があると思います。しかし、プロセスによって絶対時間またはCPU使用量を測定しようとしていますか?
ディップスティック

2
SOFに言いたかっただけで、再び私のベーコンが保存されました;-)
corlettk

回答:


92

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私のマシンに戻ります。


30
わずかな注意:gettimeofday()は単調ではありません。たとえば、マシンがネットワークタイムサーバーや他のタイムソースとの同期を維持しようとしている場合、ジャンプ(逆戻りさえ)する可能性があります。
ディップスティック

3
正確に言うと、ISO C99(この部分ではANSI Cと互換性があると思います)では時間分解能の保証さえありません。(ISO C99、7.23.1p4)
Roland Illig、

6
timeval::tv_usec常に1秒未満であり、ループしていることに注意してください。1秒よりも大きな時間の違いを取るために、すなわち、あなたがすべきである:long usec_diff = (e.tv_sec - s.tv_sec)*1000000 + (e.tv_usec - s.tv_usec);
アレクサンダーマラコフ

4
@Dipstick:ただし、たとえばNTPは、明示的に指示するまで時計を後方に移動することはありません。
thejh 2013年

1
@AlexanderMalakhov時間減算ロジックは、関数内にカプセル化されtimersubます。tval_result値(tv_secおよびtv_usec)をそのまま使用できます。
x4444

48
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);

CLOCKS_PER_SECは、多くのシステムで1000000に設定されています。この方法で使用する前に、その値を印刷して確認してください。
ysap 2012年

16
1秒あたりのクロック数なので、値は関係ありません。clock()/ CLOCKS_PER_SECからの結果の値は秒単位になります(少なくともそれは必要です)。1000で割ると、ミリ秒になります。
デビッドヤング

14
Cリファレンスマニュアルによると、clock_t値は約36分からラップアラウンドできます。長い計算を測定している場合は、これに注意する必要があります。
Cyber​​Skull 2013

4
また、整数の除算CLOCKS_PER_SEC / 1000が不正確になる可能性があることにも注意してください(これは、私の経験でCLOCKS_PER_SECは常に1000の倍数でしたが)。行うこと(1000 * clock()) / CLOCKS_PER_SECは、分割の不正確さの影響を受けにくくなりますが、一方でオーバーフローの影響を受けやすくなります。考慮すべきいくつかの問題。
Cornstalks 2013

4
これはCPU時間を測定し、実時間を測定しませんか?
krs013 2015

27

私は常に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);
}

5
clock_gettime()は、ANSI Cではない
PowerApp101

1
また、CLOCK_MONOTONICは多くのシステム(多くのLinuxプラットフォームを含む)では実装されていません。
ディップスティック

2
@ PowerApp101これを行うには良い/堅牢なANSI Cの方法はありません。他の回答の多くはANCI CではなくPOSIXに依存しています。@Dipstick今日、私は現代のほとんどのプラットフォーム[要出典]のサポートを信じclock_gettime(CLOCK_MONOTONIC, ...)ており、機能テストマクロさえある_POSIX_MONOTONIC_CLOCK
omn​​inonsense 2015

22

ポータブルソリューションの実装

時間測定の問題に対して十分な精度を備えた適切なANSIソリューションがないことはすでにここで述べたので、ポータブルで、可能であれば高解像度の時間測定ソリューションを取得する方法について書きたいと思います。

単調な時計とタイムスタンプ

一般的に言えば、時間測定には2つの方法があります。

  • 単調な時計;
  • 現在の(日付)タイムスタンプ。

1つ目は、事前定義された頻度でティックをカウントする単調なクロックカウンター(ティックカウンターと呼ばれることもあります)を使用するため、ティック値があり、頻度がわかっている場合、ティックを経過時間に簡単に変換できます。単調時計が現在のシステム時刻を何らかの形で反映することは実際には保証されていません。システムの起動以降、ティックをカウントすることもあります。ただし、システムの状態に関係なく、クロックが常に増加していくことが保証されます。通常、周波数はハードウェアの高解像度ソースにバインドされているため、高精度が提供されます(ハードウェアによって異なりますが、最新のハードウェアのほとんどには高解像度クロックソースの問題はありません)。

2番目の方法は、現在のシステムクロック値に基づいて(日付)時刻値を提供します。高解像度の場合もありますが、1つの大きな欠点があります。この種類の時間値は、さまざまなシステム時間調整、つまり、タイムゾーンの変更、夏時間(DST)の変更、NTPサーバーの更新、システムの休止などの影響を受ける可能性があります。オン。状況によっては、負の経過時間の値を取得して、未定義の動作を引き起こす可能性があります。実際、この種のタイムソースは最初のものより信頼性が低くなります。

したがって、時間間隔測定の最初のルールは、可能であれば単調クロックを使用することです。通常は精度が高く、設計上信頼性があります。

フォールバック戦略

ポータブルソリューションを実装する場合は、フォールバック戦略を検討する価値があります。システムに単調クロックがない場合は、単調クロックを使用し、タイムスタンプへのフォールバックアプローチを使用します。

ウィンドウズ

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は、システムが起動してから経過したミリ秒数を提供します。49.7日ごとに折り返されるため、長い間隔を測定する場合は注意してください。
  • GetTickCount64は64ビットバージョンのですがGetTickCount、Windows Vista以降で使用できます。

OS X(macOS)

OS X(macOS)には、単調な時計を表す独自のマッハ絶対時間単位があります。開始する最良の方法は、Appleの記事「テクニカル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

IRIXclock_gettime電話をかけましたが、それは欠けていCLOCK_MONOTONICます。代わりに、として、独自の単調なクロックソースが定義されていCLOCK_SGI_CYCLEますが、代わりに使用すべきCLOCK_MONOTONICclock_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_gettimegethrtimeありませんが、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を参照してください)。


「時間測定の問題のために十分な精度とは、適切なANSIソリューションは存在しない」:C11があるtimespec_getstackoverflow.com/a/36095407/895245
チロSantilli郝海东冠状病六四事件法轮功

1
これは、コード実行の時間を測定するための誤った方法です。timespec_get単調ではありません。
Alexander Saprykin 2017年

11

timespec_get C11から

実装の解像度に丸められたナノ秒までを返します。

POSIXからのANSI詐欺のように見えclock_gettimeます。

例:a 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_clockC ++ 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現在のみサポートされています

  • __clock_gettime (CLOCK_REALTIME, ts)POSIX APIであるに転送します: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?


これは2017年時点で正解です。MSVCにもこの機能があります。ベンチマークに関しては、チップレジスタを読み取るものを探します(PT拡張機能を備えたx86プロセッサの新しいバージョン、および対応するLinuxカーネル/ perfの新しいバージョン)

4

あなたが得ることができる最高の精度は、クロックレベルの解決を提供できるx86のみの「rdtsc」命令の使用によるものです(もちろん、簡単に測定できるrdtsc呼び出し自体のコストを考慮する必要があります)アプリケーションの起動)。

ここでの主な問題は、毎秒のクロック数を測定することです。


3
一部のマシンではRDTSC呼び出しを複数のプロセッサーに送信し、それらのRDTSCカウンターが同期されない場合があるため、プロセッサーのアフィニティーについても考慮する必要がある場合があります。
ウィルディーン

1
さらに、一部のプロセッサではTSCが単調に増加していません。CPU周波数を下げる省電力モードを考えてください。何もなく、ためにRDTSCを使用して非常に短いローカライズされたタイミングは、ある非常に悪い考え。
スネマーチ2013

ところで、@ WillDeanが言及し、タイミングにrdtscを使用しているコアドリフトは、多くのゲームが(初期?)マルチコアAMD64 CPUで機能しなかった理由です。タイトルの数。
スネマーチ2013

2

受け入れられた答えは十分ですが、私の解決策はより単純です.Linuxでテストするだけで、gcc(Ubuntu 7.2.0-8ubuntu3.2)7.2.0を使用します。

Alse使用はgettimeofdaytv_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秒。


-4

ウィンドウの下:

SYSTEMTIME t;
GetLocalTime(&t);
swprintf_s(buff, L"[%02d:%02d:%02d:%d]\t", t.wHour, t.wMinute, t.wSecond, t.wMilliseconds);

1
このANSI Cは要求どおりですか?
Gyom 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.