読者です 読者をやめる 読者になる 読者になる

Natural Software

KinectなどのDepthセンサーを中心に活動しています

Windows で高分解能タイマ

C++ Win32

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 の埋め込みがうまくできなかった・・・