web-dev-qa-db-ja.com

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

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

116
corto

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を返します。

86
Robert Gamble
#include <time.h>
clock_t uptime = clock() / (CLOCKS_PER_SEC / 1000);
44
Nick Van Brunt

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

ここで、時間測定の問題に対して十分な精度を備えた適切な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 は、システムが起動してから経過したミリ秒数を提供します。 49.7日ごとにラップされるため、長い間隔を測定する場合は注意してください。
  • GetTickCount64GetTickCountの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_CLOCK0に定義されている場合、実行時に動作するかどうかをさらに確認する必要があることを意味します。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_CYCLECLOCK_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を参照してください)。

19

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秒あたりのクロック数を測定することです。これはそれほど難しくないはずです。

4
Dark Shikari

受け入れられた答えは十分ですが、私のソリューションはよりシンプルです。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秒。

1
Lee