Windows時間計測APIの精度を徹底検証
Windows環境で経過時間を測る際、多くの開発者は GetTickCount を真っ先に思い浮かべるかもしれません。しかし、高精度な計測が求められる場面では QueryPerformanceCounter の名が挙がり、両者のどちらを選ぶべきか迷うケースも珍しくありません。本記事では二つのAPIの仕組みや精度の違いを整理し、用途に応じた選び方を探ります。
どちらも Windows API に含まれる時間取得関数ですが、内部的に参照するカウンタの種類や精度、更新頻度に明確な差があります。ミリ秒単位で大雑把な経過時間が欲しいだけなら GetTickCount で十分ですが、サブミリ秒の精度が必要になれば話は別です。それぞれの特性を見極めたうえで実装に取り入れるのが望ましく、関連知識を押さえるにはWindows API の資料を併用するのが効率的です。
内部で参照するカウンタとその歴史
GetTickCount は Windows が内部に持つミリ秒カウンタを返します。カーネル内の共有データ構造体に記録された値を読む仕組みで、ユーザー空間からシステムコールを発生させずに動作します。比較的古くから存在するAPIで、Windows 3.1の時代から形を変えつつ使われ続けてきました。
一方の QueryPerformanceCounter は、CPU やチップセットが備える高解像度イベントタイマー(HPET)、または RDTSC といったハードウェアカウンタを読み取って64ビット値を返します。精度はハードウェアに依存しますが、典型的にはナノ秒未満の分解能を持っています。Windows 2000 以降で正式にサポートされ、現在ではより正確な時間計測の定番となっています。
精度と呼び出しコストの実態
実際の分解能を比べると、QueryPerformanceCounter の方が圧倒的に優れます。GetTickCount の解像度はシステムタイマのティック間隔に左右され、デフォルトでは15.6ミリ秒前後の精度にとどまることも珍しくありません。QueryPerformanceCounter は周波数あたりの分解能を QueryPerformanceFrequency で取得でき、数百万から数億カウント毎秒という単位で更新されます。
呼び出しコストも無視できません。GetTickCount はキャッシュに載った共有変数を読み出すだけなので、オーバーヘッドはほぼゼロと見なせます。QueryPerformanceCounter はカウンタ読み出しに若干の命令サイクルを要しますが、それでも現代の CPU では数十ナノ秒程度です。多数回呼び出すループでは差が出るものの、現実的なアプリケーションでそれがボトルネックになることは少ないでしょう。
ラップアラウンドと長時間稼働の落とし穴
GetTickCount には32ビット符号なし整数の上限という宿命があります。およそ49.7日経過すると値が0に巻き戻り、それ以降の経過時間が負に見えてしまいます。サーバアプリケーションや長時間稼働を想定するツールでは、補正ロジックを必ず入れるべきです。
QueryPerformanceCounter は64ビット符号付き整数を返すため、現実的にラップアラウンドを意識する必要はほぼありません。数百年単位の連続稼働でも桁あふれしません。ファイルのスタンプを取る、長時間ベンチマークを収集するといった用途ではこちらが無難です。ただし Windows 7 以前の環境ではカーネルモードからの呼び出しで一部制限があった点も押さえておきたいところです。
| 項目 | GetTickCount | QueryPerformanceCounter |
|---|---|---|
| 戻り値型 | DWORD (32bit) | LARGE_INTEGER (64bit) |
| 典型的な分解能 | 約15.6 ms | 100 ns 以下 |
| ラップアラウンド | 約49.7日 | 実質なし |
| 主な参照カウンタ | システムタイマ | HPET / TSC |
| 呼び出しコスト | 極小 | 小 |
| 推奨される用途 | 大まかな経過時間 | 高精度計測・ベンチマーク |
用途別の使い分け指針
単純にミリ秒オーダーの経過時間が欲しい場合には GetTickCount が軽くて済みます。アニメーションのフレーム間待ち時間調整や、ユーザー操作の間隔判定など、そこまで精度を求めない用途であれば十分機能します。コードが短く、可読性も高いのが利点です。
サブミリ秒の測定が必要なら QueryPerformanceCounter 一択となります。プロファイリング、ベンチマーキング、フレームタイムの詳細分析、ネットワーク遅延の精密測定など、誤差が結果に直結する場面では QueryPerformanceCounter の分解能が活きてきます。計測対象に合わせて QueryPerformanceFrequency で除算するのを忘れずに。
実装で押さえておきたい注意点
QueryPerformanceCounter を使う場合、まず QueryPerformanceFrequency を一度呼び出して単位を把握しておくのが定番です。ミリ秒に変換したければ1000で、マイクロ秒なら1000000で割ります。タイムスタンプの差分計算では符号付き64ビット整数で扱った方が安全です。
GetTickCount をラップアラウンド対応で使うには、符号なし32ビットの差分変換ロジックを入れたり、GetTickCount64 への置き換えを検討したりする方法があります。GetTickCount64 は64ビットでラップアラウンドしないため、既存コードの移行先として有効です。ウインドウメッセージのフック処理など、Windows の深い仕組みを扱う場面ではタイマー動作の理解が予期せぬ挙動の回避に直結します。動作の背景を知るにはフックプロシージャの基礎が一助となるはずです。
最終的に押さえておきたいのは、GetTickCount は軽量で手軽な分、精度と長時間稼働の制約を受け入れる代わりに使う関数であり、QueryPerformanceCounter はその制約を克服した高精度版だという点です。ミリ秒で足りる場合は GetTickCount、足りなければ QueryPerformanceCounter、というシンプルな基準を自分のプロジェクトに照らして適用することで、計測誤差のないクリーンな実装にたどり着けます。