C++ を使用してナノ秒単位の時間を提供するタイマー関数

関数をループで繰り返し実行することについて他の人が投稿したことは正しいです。

Linux (および BSD) では、clock_gettime() を使用します。

#include <sys/time.h>

int main()
{
   timespec ts;
   // clock_gettime(CLOCK_MONOTONIC, &ts); // Works on FreeBSD
   clock_gettime(CLOCK_REALTIME, &ts); // Works on Linux
}

ウィンドウの場合、QueryPerformanceCounter を使用します。 QPC の詳細はこちら

一部のチップセットでは QPC に既知の問題があるようです。そのため、それらのチップセットを使用していないことを確認してください。さらに、一部のデュアル コア AMD も問題を引き起こす可能性があります。 Sebbbi による 2 番目の投稿を参照してください。彼は次のように述べています。

2013/07/16 編集:

http://msdn.microsoft.com/en-us/library/windows/desktop/ee417693(v=vs.85).aspx

ただし、この StackOverflow の回答 https://stackoverflow.com/a/4588605/34329 では、QPC は Win XP Service Pack 2 以降のどの MS OS でも正常に動作するはずであると述べています。

この記事では、Windows 7 がプロセッサに不変の TSC があるかどうかを判断し、そうでない場合は外部タイマーにフォールバックできることを示しています。 http://performancebydesign.blogspot.com/2012/03/high-resolution-clocks-and-timers-for.html プロセッサ間の同期は依然として問題です。

タイマーに関連するその他の優れた読み物:

  • https://blogs.oracle.com/dholmes/entry/inside_the_hotspot_vm_clocks
  • http://lwn.net/Articles/209101/
  • http://performancebydesign.blogspot.com/2012/03/high-resolution-clocks-and-timers-for.html
  • QueryPerformanceCounter ステータス?

詳細については、コメントを参照してください。


この新しい回答では、C++11 の 01 を使用しています 施設。 11 の使用方法を示す他の回答がありますが 、 29 の使用方法を示すものはありません 36 で ここの他のいくつかの回答で言及されている施設。そこで 41 の使い方を紹介しようと思いました 55 で .さらに、68 をすばやく切り替えることができるように、時計のテスト コードをテンプレート化する方法を示します。 およびシステムの組み込みクロック機能 (これはおそらく 71 に基づいています) 、 87 および/または 90 .

108 に注意してください 命令は x86 固有です。 119 は Windows のみです。そして 123 POSIXのみです。以下に、2 つの新しい時計を紹介します:131141 、C++11 と仮定できる場合、クロスプラットフォームになりました。

まず、Intel 150 から C++11 互換のクロックを作成する方法を次に示します。 組み立て説明書。 163 と呼ぶことにします :

#include <chrono>

namespace x
{

struct clock
{
    typedef unsigned long long                 rep;
    typedef std::ratio<1, 2'800'000'000>       period; // My machine is 2.8 GHz
    typedef std::chrono::duration<rep, period> duration;
    typedef std::chrono::time_point<clock>     time_point;
    static const bool is_steady =              true;

    static time_point now() noexcept
    {
        unsigned lo, hi;
        asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
        return time_point(duration(static_cast<rep>(hi) << 32 | lo));
    }
};

}  // x

このクロックが行うのは、CPU サイクルをカウントし、それを符号なし 64 ビット整数に格納することだけです。コンパイラのアセンブリ言語構文を微調整する必要がある場合があります。または、代わりに使用できる組み込み関数をコンパイラが提供している場合があります (例:172 ).

時計を作成するには、表現 (ストレージ タイプ) を指定する必要があります。マシンがさまざまな電力モードでクロック速度を変更する場合でも、コンパイル時定数である必要があるクロック周期も指定する必要があります。そして、それらから、これらの基本的な観点から、時計の「ネイティブ」期間と時点を簡単に定義できます。

クロック ティックの数を出力するだけの場合は、クロック周期に指定する数値は重要ではありません。この定数は、クロック ティック数をナノ秒などのリアルタイム単位に変換する場合にのみ有効です。その場合、クロック速度をより正確に指定できるほど、ナノ秒 (ミリ秒など) への変換がより正確になります。

以下は、187 の使用方法を示すコード例です。 .実際、まったく同じ構文で多くの異なる時計を使用する方法を示したいので、時計のコードをテンプレート化しました。この特定のテストは、ループの下で計測したいものを実行するときのループ オーバーヘッドがどのようなものかを示しています:

#include <iostream>

template <class clock>
void
test_empty_loop()
{
    // Define real time units
    typedef std::chrono::duration<unsigned long long, std::pico> picoseconds;
    // or:
    // typedef std::chrono::nanoseconds nanoseconds;
    // Define double-based unit of clock tick
    typedef std::chrono::duration<double, typename clock::period> Cycle;
    using std::chrono::duration_cast;
    const int N = 100000000;
    // Do it
    auto t0 = clock::now();
    for (int j = 0; j < N; ++j)
        asm volatile("");
    auto t1 = clock::now();
    // Get the clock ticks per iteration
    auto ticks_per_iter = Cycle(t1-t0)/N;
    std::cout << ticks_per_iter.count() << " clock ticks per iteration\n";
    // Convert to real time units
    std::cout << duration_cast<picoseconds>(ticks_per_iter).count()
              << "ps per iteration\n";
}

このコードが最初に行うことは、結果を表示するための「リアルタイム」単位を作成することです。ここではピコ秒を選択しましたが、整数ベースまたは浮動小数点ベースの任意の単位を選択できます。例として、既製の 191 があります 使えたはずのユニット。

別の例として、反復ごとのクロック サイクルの平均数を浮動小数点として出力したいので、クロックのティックと同じ単位 (206

ループは 217 への呼び出しのタイミングで行われます 両側に。この関数から返される型に名前を付ける場合:

typename clock::time_point t0 = clock::now();

(226 で明確に示されているように 例であり、システム提供のクロックにも当てはまります)。

浮動小数点クロック ティックで期間を取得するには、単に 2 つの時点を減算し、反復ごとの値を取得するには、その期間を反復回数で割ります。

238 を使用して、任意の期間でカウントを取得できます メンバー関数。これは内部表現を返します。最後に 249 を使用します デュレーションを変換する 252 期間 266 まで

このコードの使い方は簡単です:

int main()
{
    std::cout << "\nUsing rdtsc:\n";
    test_empty_loop<x::clock>();

    std::cout << "\nUsing std::chrono::high_resolution_clock:\n";
    test_empty_loop<std::chrono::high_resolution_clock>();

    std::cout << "\nUsing std::chrono::system_clock:\n";
    test_empty_loop<std::chrono::system_clock>();
}

上記で、自家製の 276 を使用してテストを実行します 、システム提供の 2 つのクロック 288 を使用してこれらの結果を比較します。 そして 290 .私にとって、これは次のように出力されます:

Using rdtsc:
1.72632 clock ticks per iteration
616ps per iteration

Using std::chrono::high_resolution_clock:
0.620105 clock ticks per iteration
620ps per iteration

Using std::chrono::system_clock:
0.00062457 clock ticks per iteration
624ps per iteration

これは、反復ごとのティックがクロックごとに大きく異なるため、これらのクロックのティック周期がそれぞれ異なることを示しています。ただし、既知の時間単位 (ピコ秒など) に変換すると、各クロックでほぼ同じ結果が得られます (マイレージは異なる場合があります)。

私のコードが「魔法の変換定数」から完全に解放されていることに注意してください。実際、例全体でマジック ナンバーは 2 つしかありません。

<オール>
  • 309 を定義するための私のマシンのクロック速度 .
  • テストする反復回数。この数を変更すると結果が大きく変わる場合は、反復回数を増やすか、テスト中に競合するプロセスからコンピュータを空にする必要があります。

  • そのレベルの精度では、clock() のようなシステム コールではなく、CPU ティックで推論する方がよいでしょう。そして、命令の実行に 1 ナノ秒以上かかる場合、ナノ秒の精度を持つことはほとんど不可能であることを忘れないでください。

    それでも、そのようなものは始まりです:

    CPU が最後に起動されてから渡された 80x86 CPU クロック ティックの数を取得する実際のコードを次に示します。 Pentium 以降で動作します (386/486 はサポートされていません)。このコードは、実際には MS Visual C++ 固有のものですが、インライン アセンブリをサポートしている限り、おそらく非常に簡単に他のものに移植できます。

    inline __int64 GetCpuClocks()
    {
    
        // Counter
        struct { int32 low, high; } counter;
    
        // Use RDTSC instruction to get clocks count
        __asm push EAX
        __asm push EDX
        __asm __emit 0fh __asm __emit 031h // RDTSC
        __asm mov counter.low, EAX
        __asm mov counter.high, EDX
        __asm pop EDX
        __asm pop EAX
    
        // Return result
        return *(__int64 *)(&counter);
    
    }
    

    この関数には、非常に高速であるという利点もあります。通常、実行には 50 CPU サイクルしかかかりません。

    タイミング図の使用:
    クロック カウントを実際の経過時間に変換する必要がある場合は、結果をチップのクロック速度で割ります。 「定格」GHz は、チップの実際の速度とはわずかに異なる可能性があることに注意してください。チップの実際の速度を確認するには、いくつかの非常に優れたユーティリティまたは Win32 呼び出し QueryPerformanceFrequency() を使用できます。