web-dev-qa-db-ja.com

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

APIが値を返すのにかかった時間を計算したいと思います。このようなアクションにかかる時間はナノ秒のスペースです。 APIはC++クラス/関数なので、私はtimer.hを使用して同じものを計算しています:

  #include <ctime>
  #include <cstdio>

  using namespace std;

  int main(int argc, char** argv) {

      clock_t start;
      double diff;
      start = clock();
      diff = ( std::clock() - start ) / (double)CLOCKS_PER_SEC;
      cout<<"printf: "<< diff <<'\n';

      return 0;
  }

上記のコードは、時間を秒単位で示しています。ナノ秒でより正確に同じようにするにはどうすればよいですか?

100
gagneet

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

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

明らかに、既知の issue がいくつかのチップセットでQPCにあるため、それらのチップセットがないことを確認したい場合があります。また、一部のデュアルコアAMDでも 問題 が発生する場合があります。 sebbbiの2番目の投稿を参照してください。

QueryPerformanceCounter()とQueryPerformanceFrequency()は少し優れた解像度を提供しますが、異なる問題があります。たとえば、Windows XPでは、AMDデュアルコアドライバーパッケージを特別にインストールして問題を解決しない限り、すべてのAMD Athlon X2デュアルコアCPUは、いずれかのコアのPCを「ランダムに」返します(PCが少し後方にジャンプすることがあります)。同様の問題(p4 dual、p4 ht、core2 dual、core2 quad、phenom quad)を持つ他のdual +コアCPUには気づいていません。

2013/07/16を編集:

http://msdn.Microsoft.com/en-us/library/windows/desktop/ee417693(v = vs.85)に記載されているように、特定の状況下でのQPCの有効性について論争があるようです。 .aspx

...通常、QueryPerformanceCounterとQueryPerformanceFrequencyは複数のプロセッサに合わせて調整されますが、BIOSまたはドライバのバグにより、スレッドがプロセッサ間を移動するときにこれらのルーチンが異なる値を返すことがあります...

ただし、このStackOverflowの回答 https://stackoverflow.com/a/4588605/34329 は、Win XPサービスパック2以降のMS OSでQPCが正常に動作することを示しています。

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

タイマーに関連するその他の詳細な読み取り:

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

82
grieve

この新しい答えは、C++ 11の<chrono>機能を使用しています。 <chrono>の使用方法を示す他の回答がありますが、ここの他のいくつかの回答で言及されているRDTSC機能で<chrono>を使用する方法を示すものはありません。だから、<chrono>RDTSCを使用する方法を示すと思いました。さらに、RDTSCとシステムの組み込みのクロック機能(clock()clock_gettime()および/またはQueryPerformanceCounterに基づいている可能性が高い)をすばやく切り替えることができるように、クロックのテストコードをテンプレート化する方法を示します。

RDTSC命令はx86固有であることに注意してください。 QueryPerformanceCounterはWindowsのみです。 clock_gettime()はPOSIXのみです。以下に、2つの新しいクロックstd::chrono::high_resolution_clockstd::chrono::system_clockを紹介します。これらは、C++ 11を想定できる場合、クロスプラットフォームになりました。

まず、Intel rdtsc Assembly命令からC++ 11互換のクロックを作成する方法を次に示します。 x::clockと呼びます:

#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ビット整数に格納するだけです。コンパイラのアセンブリ言語構文を微調整する必要がある場合があります。または、コンパイラが代わりに使用できる組み込み関数を提供する場合があります(例:now() {return __rdtsc();})。

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

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

以下は、x::clockの使用方法を示すサンプルコードです。実際に、まったく同じ構文で多くの異なるクロックを使用する方法を示したいので、クロックのコードをテンプレート化しました。この特定のテストは、ループの下で時間を計りたいものを実行するときのループのオーバーヘッドを示しています。

#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";
}

このコードが最初に行うことは、結果を表示する「リアルタイム」ユニットを作成することです。ピコ秒を選択しましたが、整数または浮動小数点ベースの任意のユニットを選択できます。例として、私が使用できた事前に作成されたstd::chrono::nanosecondsユニットがあります。

別の例として、反復ごとの平均クロックサイクル数を浮動小数点として出力したいので、doubleに基づいて、クロックのティックと同じ単位(コードではCycleと呼ばれる)を持つ別の期間を作成します。

ループは、両側のclock::now()の呼び出しでタイミングが取られます。この関数から返される型に名前を付けたい場合は、次のとおりです。

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

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

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

count()メンバー関数を使用して、任意の期間のカウントを取得できます。これは内部表現を返します。最後に、std::chrono::duration_castを使用して、期間Cycleを期間picosecondsに変換し、出力します。

このコードを使用するのは簡単です:

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>();
}

上記では、自家製のx::clockを使用してテストを実行し、それらの結果をシステムが提供する2つのクロックstd::chrono::high_resolution_clockstd::chrono::system_clockを使用して比較します。私にとってこれは印刷されます:

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つのマジックナンバーしかありません。

  1. x::clockを定義するためのマシンのクロック速度。
  2. テストする反復回数。この数を変更すると結果が大きく変わる場合は、おそらく反復回数を増やすか、テスト中に競合するプロセスのコンピューターを空にする必要があります。
68
Howard Hinnant

そのレベルの精度では、システムコール 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);

}

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

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

27
VonC

これを正しく行うには、RDTSCまたはclock_gettime()のいずれかの2つの方法のいずれかを使用できます。 2番目の方法は約2倍高速で、正しい絶対時間を提供できるという利点があります。 RDTSCを正しく機能させるには、示されているとおりに使用する必要があることに注意してください(このページの他のコメントにはエラーがあり、特定のプロセッサーで不適切なタイミング値が生じる場合があります)

inline uint64_t rdtsc()
{
    uint32_t lo, hi;
    __asm__ __volatile__ (
      "xorl %%eax, %%eax\n"
      "cpuid\n"
      "rdtsc\n"
      : "=a" (lo), "=d" (hi)
      :
      : "%ebx", "%ecx" );
    return (uint64_t)hi << 32 | lo;
}

およびclock_gettimeの場合:(マイクロ秒の解像度を任意に選択しました)

#include <time.h>
#include <sys/timeb.h>
// needs -lrt (real-time lib)
// 1970-01-01 Epoch UTC time, 1 mcs resolution (divide by 1M to get time_t)
uint64_t ClockGetTime()
{
    timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    return (uint64_t)ts.tv_sec * 1000000LL + (uint64_t)ts.tv_nsec / 1000LL;
}

生成されるタイミングと値:

Absolute values:
rdtsc           = 4571567254267600
clock_gettime   = 1278605535506855

Processing time: (10000000 runs)
rdtsc           = 2292547353
clock_gettime   = 1031119636
23
Marius

目的の結果を得るために次を使用しています。

#include <time.h>
#include <iostream>
using namespace std;

int main (int argc, char** argv)
{
    // reset the clock
    timespec tS;
    tS.tv_sec = 0;
    tS.tv_nsec = 0;
    clock_settime(CLOCK_PROCESS_CPUTIME_ID, &tS);
    ...
    ... <code to check for the time to be put here>
    ...
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tS);
    cout << "Time taken is: " << tS.tv_sec << " " << tS.tv_nsec << endl;

    return 0;
}
22
gagneet

C++ 11 の場合、簡単なラッパーを次に示します。

#include <iostream>
#include <chrono>

class Timer
{
public:
    Timer() : beg_(clock_::now()) {}
    void reset() { beg_ = clock_::now(); }
    double elapsed() const {
        return std::chrono::duration_cast<second_>
            (clock_::now() - beg_).count(); }

private:
    typedef std::chrono::high_resolution_clock clock_;
    typedef std::chrono::duration<double, std::ratio<1> > second_;
    std::chrono::time_point<clock_> beg_;
};

または、* nixのC++ 03の場合、

class Timer
{
public:
    Timer() { clock_gettime(CLOCK_REALTIME, &beg_); }

    double elapsed() {
        clock_gettime(CLOCK_REALTIME, &end_);
        return end_.tv_sec - beg_.tv_sec +
            (end_.tv_nsec - beg_.tv_nsec) / 1000000000.;
    }

    void reset() { clock_gettime(CLOCK_REALTIME, &beg_); }

private:
    timespec beg_, end_;
};

使用例:

int main()
{
    Timer tmr;
    double t = tmr.elapsed();
    std::cout << t << std::endl;

    tmr.reset();
    t = tmr.elapsed();
    std::cout << t << std::endl;
    return 0;
}

から https://Gist.github.com/gongzhitaao/7062087

8
gongzhitaao

一般に、関数を呼び出すのにかかる時間を計るには、1回だけではなく、何回も呼び出す必要があります。関数を1回だけ呼び出し、実行に非常に短い時間がかかる場合でも、実際にタイマー関数を呼び出すオーバーヘッドがあり、どれだけ時間がかかるかわかりません。

たとえば、関数の実行に800 nsを要すると推定する場合、ループで1,000万回呼び出します(これには約8秒かかります)。合計時間を1,000万で割って、呼び出しごとの時間を取得します。

5
Greg Hewgill

X86プロセッサで実行されているgccで次の関数を使用できます。

unsigned long long rdtsc()
{
  #define rdtsc(low, high) \
         __asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high))

  unsigned int low, high;
  rdtsc(low, high);
  return ((ulonglong)high << 32) | low;
}

digital Mars C++の場合:

unsigned long long rdtsc()
{
   _asm
   {
        rdtsc
   }
}

チップ上の高性能タイマーを読み取ります。プロファイリングを行うときにこれを使用します。

5
Walter Bright

Embedded Profiler(WindowsおよびLinuxで無料)を使用できます。これは、マルチプラットフォームタイマー(プロセッササイクルカウント)へのインターフェイスを持ち、1秒あたりのサイクル数を提供できます。

EProfilerTimer timer;
timer.Start();

... // Your code here

const uint64_t number_of_elapsed_cycles = timer.Stop();
const uint64_t nano_seconds_elapsed =
    mumber_of_elapsed_cycles / (double) timer.GetCyclesPerSecond() * 1000000000;

サイクルカウントの時間への再計算は、CPU周波数を動的に変更できる最新のプロセッサでは、おそらく危険な操作です。したがって、変換された時間が正しいことを確認するには、プロファイリングの前にプロセッサ周波数を修正する必要があります。

3
Mi-La

ここでBorlandコードを使用しているのは、ti_hundが時々負の数を与えるコードですが、タイミングはかなり良いです。

#include <dos.h>

void main() 
{
struct  time t;
int Hour,Min,Sec,Hun;
gettime(&t);
Hour=t.ti_hour;
Min=t.ti_min;
Sec=t.ti_sec;
Hun=t.ti_hund;
printf("Start time is: %2d:%02d:%02d.%02d\n",
   t.ti_hour, t.ti_min, t.ti_sec, t.ti_hund);
....
your code to time
...

// read the time here remove Hours and min if the time is in sec

gettime(&t);
printf("\nTid Hour:%d Min:%d Sec:%d  Hundreds:%d\n",t.ti_hour-Hour,
                             t.ti_min-Min,t.ti_sec-Sec,t.ti_hund-Hun);
printf("\n\nAlt Ferdig Press a Key\n\n");
getch();
} // end main
3
Paul J Moesman

1秒未満の精度が必要な場合は、システム固有の拡張機能を使用する必要があり、オペレーティングシステムのドキュメントを確認する必要があります。 POSIXは gettimeofday でマイクロ秒までサポートしますが、コンピューターの周波数が1GHzを超えていないため、これ以上正確なものはありません。

Boostを使用している場合は、 boost :: posix_time を確認できます。

3

Brock Adamsのメソッドを使用して、単純なクラスを作成します。

int get_cpu_ticks()
{
    LARGE_INTEGER ticks;
    QueryPerformanceFrequency(&ticks);
    return ticks.LowPart;
}

__int64 get_cpu_clocks()
{
    struct { int32 low, high; } counter;

    __asm cpuid
    __asm Push EDX
    __asm rdtsc
    __asm mov counter.low, EAX
    __asm mov counter.high, EDX
    __asm pop EDX
    __asm pop EAX

    return *(__int64 *)(&counter);
}

class cbench
{
public:
    cbench(const char *desc_in) 
         : desc(strdup(desc_in)), start(get_cpu_clocks()) { }
    ~cbench()
    {
        printf("%s took: %.4f ms\n", desc, (float)(get_cpu_clocks()-start)/get_cpu_ticks());
        if(desc) free(desc);
    }
private:
    char *desc;
    __int64 start;
};

使用例:

int main()
{
    {
        cbench c("test");
        ... code ...
    }
    return 0;
}

結果:

テストにかかった時間:0.0002 ms

関数呼び出しのオーバーヘッドがいくらかありますが、それでも十分に高速である必要があります:)

3
Thomas

最小限のコピー&ペースト構造+遅延使用

アイデアがクイックテストに使用できる最小限の構造を持つことである場合、C++ファイルの#includeの直後の任意の場所にコピーアンドペーストを指定することをお勧めします。これは、私がAllmanスタイルのフォーマットを犠牲にしている唯一の例です。

構造体の最初の行の精度を簡単に調整できます。指定可能な値は、nanosecondsmicrosecondsmillisecondssecondsminutes、またはhoursです。

#include <chrono>
struct MeasureTime
{
    using precision = std::chrono::microseconds;
    std::vector<std::chrono::steady_clock::time_point> times;
    std::chrono::steady_clock::time_point oneLast;
    void p() {
        std::cout << "Mark " 
                << times.size()/2
                << ": " 
                << std::chrono::duration_cast<precision>(times.back() - oneLast).count() 
                << std::endl;
    }
    void m() {
        oneLast = times.back();
        times.Push_back(std::chrono::steady_clock::now());
    }
    void t() {
        m();
        p();
        m();
    }
    MeasureTime() {
        times.Push_back(std::chrono::steady_clock::now());
    }
};

使用法

MeasureTime m; // first time is already in memory
doFnc1();
m.t(); // Mark 1: next time, and print difference with previous mark
doFnc2();
m.t(); // Mark 2: next time, and print difference with previous mark
doStuff = doMoreStuff();
andDoItAgain = doStuff.aoeuaoeu();
m.t(); // prints 'Mark 3: 123123' etc...

標準出力結果

Mark 1: 123
Mark 2: 32
Mark 3: 433234

実行後に要約が必要な場合

後でレポートが必要な場合は、たとえば、間にあるコードも標準出力に書き込むためです。次に、次の関数を構造体に追加します(MeasureTime()の直前)。

void s() { // summary
    int i = 0;
    std::chrono::steady_clock::time_point tprev;
    for(auto tcur : times)
    {
        if(i > 0)
        {
            std::cout << "Mark " << i << ": "
                    << std::chrono::duration_cast<precision>(tprev - tcur).count()
                    << std::endl;
        }
        tprev = tcur;
        ++i;
    }
}

だから、あなただけを使用することができます:

MeasureTime m;
doFnc1();
m.m();
doFnc2();
m.m();
doStuff = doMoreStuff();
andDoItAgain = doStuff.aoeuaoeu();
m.m();
m.s();

これは、前と同じようにすべてのマークをリストしますが、他のコードが実行された後です。 m.s()m.t()の両方を使用しないでください。

2
Yeti

これがLinuxの場合、「gettimeofday」関数を使用しています。これは、エポック以降の秒とマイクロ秒を示す構造体を返します。次に、timersubを使用して2つを減算して時間の差を取得し、必要な時間の精度に変換します。ただし、ナノ秒を指定すると、関数 clock_gettime() が探しているもののように見えます。渡す構造体に秒とナノ秒の観点から時間を入れます。

2
Will Mc

うまく機能するNice Boost タイマーを次に示します。

//Stopwatch.hpp

#ifndef STOPWATCH_HPP
#define STOPWATCH_HPP

//Boost
#include <boost/chrono.hpp>
//Std
#include <cstdint>

class Stopwatch
{
public:
    Stopwatch();
    virtual         ~Stopwatch();
    void            Restart();
    std::uint64_t   Get_elapsed_ns();
    std::uint64_t   Get_elapsed_us();
    std::uint64_t   Get_elapsed_ms();
    std::uint64_t   Get_elapsed_s();
private:
    boost::chrono::high_resolution_clock::time_point _start_time;
};

#endif // STOPWATCH_HPP


//Stopwatch.cpp

#include "Stopwatch.hpp"

Stopwatch::Stopwatch():
    _start_time(boost::chrono::high_resolution_clock::now()) {}

Stopwatch::~Stopwatch() {}

void Stopwatch::Restart()
{
    _start_time = boost::chrono::high_resolution_clock::now();
}

std::uint64_t Stopwatch::Get_elapsed_ns()
{
    boost::chrono::nanoseconds nano_s = boost::chrono::duration_cast<boost::chrono::nanoseconds>(boost::chrono::high_resolution_clock::now() - _start_time);
    return static_cast<std::uint64_t>(nano_s.count());
}

std::uint64_t Stopwatch::Get_elapsed_us()
{
    boost::chrono::microseconds micro_s = boost::chrono::duration_cast<boost::chrono::microseconds>(boost::chrono::high_resolution_clock::now() - _start_time);
    return static_cast<std::uint64_t>(micro_s.count());
}

std::uint64_t Stopwatch::Get_elapsed_ms()
{
    boost::chrono::milliseconds milli_s = boost::chrono::duration_cast<boost::chrono::milliseconds>(boost::chrono::high_resolution_clock::now() - _start_time);
    return static_cast<std::uint64_t>(milli_s.count());
}

std::uint64_t Stopwatch::Get_elapsed_s()
{
    boost::chrono::seconds sec = boost::chrono::duration_cast<boost::chrono::seconds>(boost::chrono::high_resolution_clock::now() - _start_time);
    return static_cast<std::uint64_t>(sec.count());
}
2
Patrick K

あれについてどう思う:

    int iceu_system_GetTimeNow(long long int *res)
    {
      static struct timespec buffer;
      // 
    #ifdef __CYGWIN__
      if (clock_gettime(CLOCK_REALTIME, &buffer))
        return 1;
    #else
      if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &buffer))
        return 1;
    #endif
      *res=(long long int)buffer.tv_sec * 1000000000LL + (long long int)buffer.tv_nsec;
      return 0;
    }
2
icegood