QueryPerformanceCounterの使用方法


97

私は最近、Timerクラスのミリ秒からマイクロ秒に変更する必要があると判断し、いくつかの調査の結果、QueryPerformanceCounterがおそらく最も安全であると判断しました。(Boost::PosixWin32 APIでは機能しない可能性があるという警告が表示されて、少し遅れました)。しかし、私はそれを実装する方法が本当にわかりません。

私がやっているGetTicks()ことは、使用しているesque関数を呼び出して、それをTimerのstartingTicks変数に代入することです。次に、経過時間を見つけるために、関数の戻り値をから減算しstartingTicks、タイマーをリセットしたら、関数をもう一度呼び出して、startingTicksをそれに割り当てます。残念ながら、私が見たコードではQueryPerformanceCounter()、を呼び出すだけの単純なものではなく、引数として何を渡すべきかわかりません。


2
Ramonsterのコードスニペットを取得して、ここにライブラリを作成しました:フォロワー向けのgist.github.com/1153062
rogerdpack 2011

3
最近、QueryPerformanceCounterのドキュメントを更新し、適切な使用方法に関する追加情報とFAQへの回答を追加しました。あなたはここに更新されたドキュメントを見つけることができますmsdn.microsoft.com/en-us/library/windows/desktop/...
エド・ブリッグス

__rdtscについて言及するのと同じように、これはQueryPerformanceCounterが使用するものです。
コリンラマール2017

回答:


159
#include <windows.h>

double PCFreq = 0.0;
__int64 CounterStart = 0;

void StartCounter()
{
    LARGE_INTEGER li;
    if(!QueryPerformanceFrequency(&li))
    cout << "QueryPerformanceFrequency failed!\n";

    PCFreq = double(li.QuadPart)/1000.0;

    QueryPerformanceCounter(&li);
    CounterStart = li.QuadPart;
}
double GetCounter()
{
    LARGE_INTEGER li;
    QueryPerformanceCounter(&li);
    return double(li.QuadPart-CounterStart)/PCFreq;
}

int main()
{
    StartCounter();
    Sleep(1000);
    cout << GetCounter() <<"\n";
    return 0;
}

このプログラムは1000に近い数値を出力する必要があります(Windowsのスリープはそれほど正確ではありませんが、999のようにする必要があります)。

このStartCounter()関数は、パフォーマンスカウンターがCounterStart変数に持つティック数を記録します。このGetCounter()関数は、StartCounter()最後に呼び出されてからのミリ秒数をdoubleとしてGetCounter()返します。したがって、0.001が返される場合は、呼び出されてから約1マイクロ秒StartCounter()です。

タイマーに秒を使用させたい場合は、変更してください

PCFreq = double(li.QuadPart)/1000.0;

PCFreq = double(li.QuadPart);

または、マイクロ秒が必要な場合は、

PCFreq = double(li.QuadPart)/1000000.0;

しかし、それはdoubleを返すので、本当に便利です。


5
正確には、LARGE_INTEGERとは何ですか?
匿名

5
これはWindowsタイプであり、基本的には移植可能な64ビット整数です。これは、ターゲットシステムが64ビット整数をサポートしているかどうかによって異なります。システムが64ビット整数をサポートしていない場合は、HighPartとLowPartの2つの32ビット整数として定義されます。システムが64ビット整数をサポートしている場合、それは2つの32ビット整数とQuadPartと呼ばれる64ビット整数の間の和集合です。
ラモンスター2009年

8
この答えはひどく欠陥があります。QueryPerformanceCounterはコア固有のサイクルカウンターレジスタを読み取ります。実行のスレッドが別のコアで再スケジュールされている場合、QueryPerformanceCounterからの2つの測定は、経過時間だけでなく、多くの場合、2つのコアレジスタ間のデルタを正確に特定するのが難しい固定の大きなデルタを組み込みます。したがって、これは、プロセスが特定のコアにバインドされている場合にのみ、提示されたとおりに確実に機能します。
トニー・デルロイ

15
@TonyD:MSDNのドキュメントによると:On a multiprocessor computer, it should not matter which processor is called. However, you can get different results on different processors due to bugs in the basic input/output system (BIOS) or the hardware abstraction layer (HAL).このコードにはひどい欠陥はありませんが、いくつかのBIOSまたはHALに欠陥があります。
ルーカス

3
@TonyD:私はこれについてもう少し調べました。次の呼び出しをStartCounter関数に追加しました。old_mask = SetThreadAffinityMask(GetCurrentThread,1);次に、最後に戻しSetThreadAffinityMask ( GetCurrentThread , old_mask ) ;ます。私はそれがうまくいくことを願っています。これにより、スレッドが1番目のCPUコア以外に再スケジュールされるのを防ぐことができます。(これは明らかにテスト環境のソリューションにすぎません)
Lucas

19

私はこれらの定義を使用します:

/** Use to init the clock */
#define TIMER_INIT \
    LARGE_INTEGER frequency; \
    LARGE_INTEGER t1,t2; \
    double elapsedTime; \
    QueryPerformanceFrequency(&frequency);


/** Use to start the performance timer */
#define TIMER_START QueryPerformanceCounter(&t1);

/** Use to stop the performance timer and output the result to the standard stream. Less verbose than \c TIMER_STOP_VERBOSE */
#define TIMER_STOP \
    QueryPerformanceCounter(&t2); \
    elapsedTime=(float)(t2.QuadPart-t1.QuadPart)/frequency.QuadPart; \
    std::wcout<<elapsedTime<<L" sec"<<endl;

使用法(再定義を防ぐための括弧):

TIMER_INIT

{
   TIMER_START
   Sleep(1000);
   TIMER_STOP
}

{
   TIMER_START
   Sleep(1234);
   TIMER_STOP
}

使用例からの出力:

1.00003 sec
1.23407 sec

2

あなたがWindowsを使用していると仮定すると(もしそうならあなたはあなたの質問にタグを付ける必要があります!)、このMSDNページでHRTimer、必要なシステムコールをラップして必要なものに非常に近い何かを行うシンプルで便利なC ++クラスのソースを見つけることができます(GetTicks()特に、必要なことを正確に実行するために、メソッドを追加するのは簡単です)。

Windows以外のプラットフォームでは、QueryPerformanceCounter関数がないため、ソリューションを直接移植することはできません。ただし、上記のようなクラスでラップする場合はHRTimer、現在のプラットフォームが実際に提供できるものを使用するようにクラスの実装を変更する方が簡単です(たぶんBoostなどを介して!)。


1

時間の取得に関するNDISドライバーの例でこの質問を拡張します。ご存知のように、KeQuerySystemTime(NdisGetCurrentSystemTimeの下で模倣される)はミリ秒を超える低解像度であり、ネットワークパケットや他のIRPなど、より良いタイムスタンプを必要とするプロセスがあります。

例は同じくらい簡単です:

LONG_INTEGER data, frequency;
LONGLONG diff;
data = KeQueryPerformanceCounter((LARGE_INTEGER *)&frequency)
diff = data.QuadPart / (Frequency.QuadPart/$divisor)

ここで、除数は必要な解像度に応じて10 ^ 3または10 ^ 6です。

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