Windows で高分解能タイマといえば time〜 のマルチメディアタイマを使うけど、実はこれも 1msec 弱の遅延があるらしい。
ここからさらに高分解能のカウンタ QueryPerformanceCounter を組み合わせて精度上げてみる。
ケース1・timeSetEvent のみ
10msec を計るために 1msec の分解能に設定した timeSetEvent を 10msec タイマとして使用する。
下のようなコードを動かすとおおむね 700μsec ほどの遅延が発生していることがわかる。
コード
#include <windows.h> #include <mmsystem.h> #include <cstdio> #pragma comment( lib, "winmm.lib" ) void CALLBACK TimerCallback( UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2 ) { LONGLONG freq; LONGLONG end; LONGLONG begin = *(LONGLONG*)dwUser; ::QueryPerformanceFrequency( (LARGE_INTEGER*)&freq ); ::QueryPerformanceCounter( (LARGE_INTEGER*)&end ); printf( "%lf\n", double(end - begin) / freq * 1000 ); } void main() { LONGLONG begin; ::timeBeginPeriod( 1 ); for ( int i = 0; i < 10; ++i ) { ::QueryPerformanceCounter( (LARGE_INTEGER*)&begin ); ::timeSetEvent( 10, 1, ::TimerCallback, (DWORD)&begin, TIME_ONESHOT ); ::Sleep( 500 ); } } // EOF
ケース2・timeSetEvent で大まかな時間を計り、QueryPerformanceCounter で微調整する
10msec を計るために 1msec の分解能に設定した timeSetEvent を 9msec タイマとして使用し、QueryPerformanceCounter で微調整する。
下のようなコードを動かすと遅延が 2μsec 以内に収まっている。
コード
#include <windows.h> #include <mmsystem.h> #include <cstdio> #pragma comment( lib, "winmm.lib" ) void CALLBACK TimerCallback( UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2 ) { LONGLONG freq; LONGLONG end; LONGLONG begin = *(LONGLONG*)dwUser; ::QueryPerformanceFrequency( (LARGE_INTEGER*)&freq ); do { ::QueryPerformanceCounter( (LARGE_INTEGER*)&end ); } while ( ((end - begin) * 1000 / freq) < 10 ); printf( "%lf\n", double(end - begin) / freq * 1000 ); } void main() { LONGLONG begin; ::timeBeginPeriod( 1 ); for ( int i = 0; i < 10; ++i ) { ::QueryPerformanceCounter( (LARGE_INTEGER*)&begin ); ::timeSetEvent( 9, 1, ::TimerCallback, (DWORD)&begin, TIME_ONESHOT ); ::Sleep( 500 ); } } // EOF
まとめ
Windows で 10msec くらいの処理ってまずムリだろうと思ってたけど意外とできるもんなんだね。
プロセス、スレッドの優先度を上げればさらに確実になるのだろうか。
プロジェクト一式(VC6.0)
http://cid-a44057a0a91e490c.skydrive.live.com/self.aspx/program/C++/PerformanceCounterTest.zip
#SkyDrive の埋め込みがうまくできなかった・・・