QueryPerformanceCounterをもうちょっと検証してみよう

前回、QueryPerformanceCounterを実時間計測には使えないの記事で

つまり、負荷が変動するような環境でQueryPerformanceCounterを使っても正しい実時間は不明ということになります。

実時間でなくていい場合(計算にかかったクロック数を比較するような場合)では使用してもそれなりの効果を得られる「場合もある」わけです。

ということを書きましたが、drednoteさんの、.NETのStopwatchクラスは果たして常に正確か?の記事の中の一言

個人的には、そもそもSleep(1000)で正確に1秒きっかり停止しているかどうかから検証すべきな気もしてますが

をみて、う゛っ・・・と思いました・・・。

まあ、確かにそれを検証してからやるべき何でしょうね・・・。

というわけで、この問題を再検証してみました。コードは以下の通りです。

#include
#include
#include
#pragma comment(lib,”winmm.lib”)
#define _USE_TIMECAPS_
int main(void)
{
LARGE_INTEGER liBegin,liEnd,liFreq; DWORD dwBegin,dwEnd; int i;
#ifdef _USE_TIMECAPS_
TIMECAPS timeCaps;
timeGetDevCaps(&timeCaps,sizeof(timeCaps)); timeBeginPeriod(timeCaps.wPeriodMin);
#endif
dwBegin = dwEnd = 0;
for(i = 0;i < 10;i++){ dwBegin = timeGetTime(); QueryPerformanceCounter(&liBegin); Sleep(1000); QueryPerformanceCounter(&liEnd); dwEnd = timeGetTime(); QueryPerformanceFrequency(&liFreq); printf("%4d:%f(%lld)\n",dwEnd - dwBegin,(double)(liEnd.QuadPart - liBegin.QuadPart) / (double)liFreq.QuadPart,liFreq.QuadPart); } #ifdef _USE_TIMECAPS_ timeEndPeriod(timeCaps.wPeriodMin); #endif return 0; } [/cpp]

わかると思いますが、Sleep(1000)の正当性(つまり、Sleep(1000)は1000msの動作停止を行うこと)を確認するためにtimeGetTimeで時間計測を行うようにしたものです。

なお、timeBeginPeriod~timeEndPeriodは実行すると時間分解能が確保できるのですがその代わりCPUに負荷を与える(正確な時間を計ろうとするとその分割り込みが増えるのかな?)ので

本来はない方がいいのですが、そうなると時間の検証が弱くなるので入れるようにしてあります。

このソースコードで以下の条件で実行してその結果の8行目だけをピックアップして出してみたいと思います。

8行目なのは、10行ともほとんど同じ結果だったので結果例として出しています。

  • 検証OS:WinXP 32bit,Win7 64bit
  • CPUクロック測定:CPU-Z (実行させながら目視で確認)
  • 状態:無負荷(Clock:2GHz),WMPでSVGAサイズのMPEGの再生(Clock:2GHz<=>2.66GHz[変動]),CPUベンチマーク時(Clock:2.66GHz)

結果は以下の通りでした。

WinXP 32bit 無負荷 1000:0.634721(2666410000)
WinXP 32bit MPEG再生 1000:0.741520(2666410000)
WinXP 32bit CPUベンチ 1000:1.001017(2666410000)
Win7 64bit 無負荷 1000:0.999988(2603925)
Win7 64bit MPEG再生 1000:1.000035(2603925)
Win7 64bit CPUベンチ 1000:1.000019(2603925)

この結果からわかることは、私のPC環境では、

  • WinXP 32bitでは、QueryPerformanceCounterにCPUクロックを使っていると思われる。そのため、CPUクロックの変動でカウンタの速度が変化してしまって正しく計測できていない。
  • Win7 64bitでは、QueryPerformanceCounterに別のリファレンスクロックを使っている(2.6MHzを示している)のでCPUクロックの変動に依存しなくなっている。ただし、リファレンスクロックの周波数そのものはWinXPよりも下がっているので分解能は少し悪くなっている。

・・・OSが違うと結果が違うのかい。・・・そうですかい・・・・・・。

もちろん、PC環境が違ってもQueryPerformanceCounterが使用する高分解能パフォーマンスカウンタは違うものになると思われるので一概に言えないと思います。

なお、.NET側の仕様(というより実装)はよくわかりませんが、対象としているSystem.Diagnostics.Stopwatch.Frequencyフィールドの説明にも

Frequency 値は、基になるタイミング機構の解像力によって異なります。インストールされているハードウェアおよびオペレーティング システムが

高解像力のパフォーマンス カウンタをサポートする場合、Frequency 値はそのカウンタの頻度を反映します。それ以外の場合、Frequency 値はシステム タイマ頻度に基づきます。

と書いてあるので、おそらくはQueryPerformanceCounterを使うような実装なんだろうな~と思います。

こういう結果に結局なっちゃうんですね・・・。

ちなみに気になる人は上記のソースコードを適当なコンパイラ(VC++あたり?)でビルドとして実験してみればいいと思います。

5/5 3:30追記

ちょっと気になって

じゃあ、高負荷時にツールを使ってオーバークロックするとどうなるの?

という疑問を調べてみました。結果はこんな感じ。オーバークロック時のクロックは2.73GHz(約102.6%)でした。

WinXP 32bit OC時 1000:1.025935(2666400000)
Win7 64bit OC時 1000:1.000026(2603886)

やっぱりそうなのか・・・。(なお、QueryPerformanceFrequencyの値は一度再起動しているので変化しています)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

この記事のトラックバック用URL