Windows の Timer について †
結論:正確な時間計測には QueryPerformanceCounter(), QueryPerformanceFrequency() を使いなさいってこった。
Windows はリアルタイム性を強く求めていないので、Timer としての精度は悪い。
WM_TIMER の優先順位はかなり低く、他のプロセスに CPU パワーの優先順位が高く割り当てられている等の場合、100ms 程度の制度しか期待できない。システム全体のパフォーマンスの均一化によるパフォーマンスの向上を考えると一概に悪い仕様とは言えないだろう。
逆に 100ms 程度の精度で良ければ GetTickCount() で十分。
timeGetTime() †
マルチメディア用に精度を増した Timer。Windows では老舗に当たるが今では互換性のために用意されている API と思っていい。
10ms 程度の精度は期待できるが互換性のためだけに存在している関数なので Multi-processor に対応出来ているかが疑問だ。高い分解能を得るならQueryPerformanceCounter(), QueryPerformanceFrequency() を使用するべきだ。
RDTSC(read-time stamp counter) 命令 †
RDTSC は、CPU のクロックカウント数を返却する命令で Intel 系の Pentium 以降の CPU に実装している*1。
CPU の周波数に直結したカウント数を返却するため時間計測としては正確な部類であるとして多用されていた時期があった。しかし以下の理由により RDTSC 命令による時間計測は推奨はできない。RDTSC 命令の代わりに QueryPerformanceCounter(), QueryPerformanceFrequency() を使用するべきだ。
- 困難な Multi-Processor への対応
Processor によって取得するカウント数が異なることより、RDTSC 命令を実行する Processor を予め指定する手間が必要になる。例えば A-B 間の時間測定を行う場合、
A の時点で読み込んだ RDTSC 命令は「Processor-1」で読み込み、B の時点で読み込んだ RDTSC 命令は「Processor-2」で読み込んだ場合、正しい時間測定は期待できない。
A の時点でも B の時点でも「Processor-1」で読み込むことを保障しなければならない手間が生じる。
- 困難な変動するクロック数への対応
CPU の Power Management 機能は、内部の動作周波数を変動させる事で消費電力を下げている。これらの機能により、実測した時間とカウント数が比例しなくなる現象が
見られるようになっている。
- RDTSC 命令の正確性への疑問
Intel や AMD 製の CPU の場合、クロック数がそのまま CPU の機能に比例するので RDTSC 命令は有用だが、Transmeta 社の Efficeon の場合は CMS により内部で命令群を分解し
再構築するので、命令によってはクロック数に比例しない場合がある。Efficeon は RDTSC 命令もエミュレートしているらしいのだが、期待しているほど正確な分解能は
得られないというレポートを見たことがある。
- 高精度な Timer の台頭 (HPET, MM Timer)
RTC*2やPIT*3に置き換わる Timer として、マザーボード上(ICH4以降で実装されており、近年では AMD 系の Chipset にも実装されている)に HPET(High Precision Event Timer) という新たな Timer が実装されている。HPET は、IBM-PC 時代の互換性を踏襲した RTC や PIT より正確かつ高機能で高速なアクセスが可能である。また CPU の外部に実装されている Timer なので CPU の Power Management や Multi-processor による影響を受けない。QueryPerformanceCounter(), QueryPerformanceFrequency() にて HPET の Timer を活用する事が出来る。
QueryPerformanceCounter(), QueryPerformanceFrequency() †
Microsoft 社、一押しの Timer。今後の互換性についても心配は少ないし、様々なハードウェアを包括し、その中で最適な Timer を使用している。
RDTSC 命令より実行速度は劣るものの、Multi-processor 対応や Power Managemet 対応を考えると、その辺を包括してくれるメリットの方が高い。
使用方法として時間計測は main thread 上のみで行い他の Thread での時間計測を推奨していない。
過去の BIOS のバグを考慮し、例え Single Processor であったとしても SetThreadAffinityMask() で使用する Processor を固定することを推奨している。BIOS のバグさえ考慮に入れなければ SetThreadAffinityMask() を実行する意味合いは少ない、というか私としては好きな実装ではない。
- MSKB:896256 プロセッサ電源管理機能がサポートされているプロセッサを複数搭載したコンピュータで Windows XP Service Pack 2 を実行するとパフォーマンスが低下することがある
- MSKB:815668 How to use the QueryPerformanceCounter function to time code in Visual C++
Managed C++ による実装例。
- MSKB:306978 Visual Basic .NET または Visual Basic 2005 で QueryPerformanceCounter を使用してコードの時間を計測する方法
- MSKB:306979 [HOW TO] Visual C# .NET で、QueryPerformanceCounter を使用してコードの時間を計測する方法
- MSKB:327809 CPU 速度が 2 GHz を超えるハイパースレッド コンピュータやデュアルプロセッサ コンピュータで一部のプログラムを実行できない
関連情報 †