GetTickCount, timeGetTime等の精度について

以下は、NetNewsのfj.os.ms-windows.programmingにおいて、木屋善夫氏と検証を行った結果について簡単にまとめたものである。ここで述べる実験より以前に、さまざまな試行錯誤と議論があったが、それについて記述することは混乱を招くだけであるので、ここでは簡単に述べるにとどめる。問題を解決することになったサンプルプログラムは木屋善夫氏の手によって作成された。

 

まず、議論にあがったこととして、GetTickCountの精度は悪く、timeGetTimeを使うべきではないだろうか?ということ、またそれを調べている間に、これらの精度はOSによって異なっているということ、などが問題になった。高精度タイマとしてQueryPerformanceFrequencey()とQueryPerformanceCounter()を使うことや、タイマの精度を上げるためにtimeBeginPeriodとtimeEndPeriodを使うことなどが、議論にあがる。

 

実験

以下のテストプログラム(木屋善夫氏制作)を使用して実験を行った。

//======================================================
HANDLE hThread = GetCurrentThread( );
int iCurPriority = GetThreadPriority( hThread );
if ( !SetThreadPriority( hThread, THREAD_PRIORITY_TIME_CRITICAL ) )
    return;

//======================================================
enum { TIME = 1000, };
struct tagSData
{
    DWORD dwGTC;
    DWORD dwTGT;
    DWORD dwQPC;
} lst[TIME];

//======================================================
LARGE_INTEGER stQPF;
LARGE_INTEGER stQPC;
QueryPerformanceFrequency( &stQPF );
DWORD dwStep = stQPF.LowPart / 1000;

QueryPerformanceCounter( &stQPC );
DWORD dwLastTime = stQPC.LowPart;

//======================================================
for ( int i = 0 ; i < TIME ; ++ i )
{
    do
    {
        QueryPerformanceCounter( &stQPC );
    }
    while ( (stQPC.LowPart - dwLastTime) < dwStep );

    lst[i].dwGTC = GetTickCount( );
    lst[i].dwTGT = timeGetTime( );
    lst[i].dwQPC = stQPC.LowPart;

    dwLastTime = stQPC.LowPart;
}

//======================================================
SetThreadPriority( hThread, iCurPriority );

その結果がこれである。投稿されたリストは長いものであるが、ほぼ同じ傾向が続くため割愛した。

============================================================
使用マシン Pentium II 266 Windows 98

GTC, TGT, QPC, 前回GTCとの差, 同TGT, 同QPC, mSec(QPCより)
# GTC = GetTickCount
# TGT = timGetTime
# QPC = QueryPerformanceCounter
# QueryPerformanceFrequency = 1193180

7240449, 7240450, 47922972, -, -, ----, -----------
7240450, 7240451, 47924166, 1, 1, 1194, 1.000687239
7240451, 7240452, 47925359, 1, 1, 1193, 0.999849143
7240452, 7240453, 47926554, 1, 1, 1195, 1.001525336
7240453, 7240454, 47927748, 1, 1, 1194, 1.000687239
7240454, 7240455, 47928941, 1, 1, 1193, 0.999849143
7240455, 7240456, 47930136, 1, 1, 1195, 1.001525336
7240456, 7240457, 47931330, 1, 1, 1194, 1.000687239
7240457, 7240458, 47932529, 1, 1, 1199, 1.004877722
7240458, 7240459, 47933728, 1, 1, 1199, 1.004877722
7240459, 7240460, 47934923, 1, 1, 1195, 1.001525336

そして、この結果について木屋氏からの説明。

 さらなるテスト結果です。例によって10台ほどテストしましたが、マシンによる違いはあまりありませんでした。

 今回は、QueryPerformanceCounter を使って、(アバウトではありますが) 1mSec 毎に、GetTickCount, timeGetTime を呼び出しその値を表にしてみました。

 表の通りほぼ正確な値を返しておりますので、精度は、1mSec と考えて差し支えないと思います。

 記事の行数が増えてしまうので NT(2000) での結果は割愛しましたが、精度は 10mSec でした。(^_^;

 一連の実験の結果、GetTickCount の精度は、それほど問題ないが、Sleep の精度はシャレにならないと感じました。

しかし、先のソースに対して、以下のようにリマーク

//    lst[i].dwGTC = GetTickCount( );

とした場合と、

//    lst[i].dwTGT = timeGetTime( );

とで、実験結果が異なり、更なる調査を行う。以下は私の投稿。

> //======================================================
> for ( int i = 0 ; i < TIME ; ++ i )
> {
>     do
>     {
>         QueryPerformanceCounter( &stQPC );
>     }
>     while ( (stQPC.LowPart - dwLastTime) < dwStep );

>     lst[i].dwGTC = GetTickCount( );
>     lst[i].dwTGT = timeGetTime( );
>     lst[i].dwQPC = stQPC.LowPart;

>     dwLastTime = stQPC.LowPart;
> }

まず、これを普通に実行します。こんな感じ。

49317149,49317150,3000785231, 1, 1, 1194, 1.000687
49317150,49317151,3000786425, 1, 1, 1194, 1.000687
49317151,49317152,3000787620, 1, 1, 1195, 1.001525
49317152,49317153,3000788814, 1, 1, 1194, 1.000687
49317153,49317154,3000790009, 1, 1, 1195, 1.001525
49317154,49317155,3000791202, 1, 1, 1193, 0.999849
49317155,49317156,3000792396, 1, 1, 1194, 1.000687
49317156,49317157,3000793591, 1, 1, 1195, 1.001525
49317157,49317158,3000794784, 1, 1, 1193, 0.999849
49317158,49317159,3000795977, 1, 1, 1193, 0.999849

次に上記のこの部分をコメントアウトします。
> // lst[i].dwTGT = timeGetTime( );

49454850, 0,3165061469,14, 0, 1447, 1.212726
49454850, 0,3165062662, 0, 0, 1193, 0.999849
49454850, 0,3165063859, 0, 0, 1197, 1.003202
49454850, 0,3165065055, 0, 0, 1196, 1.002363
49454850, 0,3165066251, 0, 0, 1196, 1.002363
49454850, 0,3165067447, 0, 0, 1196, 1.002363
49454850, 0,3165068644, 0, 0, 1197, 1.003202
49454850, 0,3165069841, 0, 0, 1197, 1.003202
49454850, 0,3165071038, 0, 0, 1197, 1.003202
49454850, 0,3165072235, 0, 0, 1197, 1.003202
49454850, 0,3165073431, 0, 0, 1196, 1.002363
49454850, 0,3165074627, 0, 0, 1196, 1.002363
49454850, 0,3165075824, 0, 0, 1197, 1.003202
49454850, 0,3165077017, 0, 0, 1193, 0.999849
49454863, 0,3165078213,13, 0, 1196, 1.002363
49454863, 0,3165079411, 0, 0, 1198, 1.004040
49454863, 0,3165080607, 0, 0, 1196, 1.002363
49454863, 0,3165081803, 0, 0, 1196, 1.002363
49454863, 0,3165082999, 0, 0, 1196, 1.002363
49454863, 0,3165084196, 0, 0, 1197, 1.003202
49454863, 0,3165085392, 0, 0, 1196, 1.002363
49454863, 0,3165086589, 0, 0, 1197, 1.003202
49454863, 0,3165087786, 0, 0, 1197, 1.003202
49454863, 0,3165088982, 0, 0, 1196, 1.002363
49454863, 0,3165090179, 0, 0, 1197, 1.003202
49454863, 0,3165091374, 0, 0, 1195, 1.001525
49454863, 0,3165092572, 0, 0, 1198, 1.004040
49454863, 0,3165093768, 0, 0, 1196, 1.002363

次にこの部分をSleep(1)に置き換えます。(注)(0)では変化なし。
> Sleep(1);//lst[i].dwTGT = timeGetTime( );

49604448, 0,3343532183,14, 0,16272,13.637506
49604462, 0,3343548553,14, 0,16370,13.719640
49604475, 0,3343564941,13, 0,16388,13.734726
49604489, 0,3343581440,14, 0,16499,13.827754
49604503, 0,3343598894,14, 0,17454,14.628137
49604518, 0,3343616263,15, 0,17369,14.556898
49604530, 0,3343630491,12, 0,14228,11.924437
49604531, 0,3343631689, 1, 0, 1198, 1.004040
49604544, 0,3343647029,13, 0,15340,12.856401
49604558, 0,3343663277,14, 0,16248,13.617392
49604572, 0,3343679670,14, 0,16393,13.738916
49604585, 0,3343696036,13, 0,16366,13.716288
49604599, 0,3343712566,14, 0,16530,13.853735
49604613, 0,3343728774,14, 0,16208,13.583868
49604626, 0,3343745164,13, 0,16390,13.736402
49604640, 0,3343761549,14, 0,16385,13.732211
49604654, 0,3343778045,14, 0,16496,13.825240
49604668, 0,3343794333,14, 0,16288,13.650916

さらに、
timeBeginPeriod( 1 );
timeEndPeriod( 1 );
を用いてSleepの精度を上げます。

49740987, 0,3506423818, 1, 0, 1195, 1.001525
49740988, 0,3506425014, 1, 0, 1196, 1.002363
49740989, 0,3506426212, 1, 0, 1198, 1.004040
49740990, 0,3506427406, 1, 0, 1194, 1.000687
49740991, 0,3506428603, 1, 0, 1197, 1.003202
49740993, 0,3506430061, 2, 0, 1458, 1.221945
49740994, 0,3506432106, 1, 0, 2045, 1.713907
49740995, 0,3506433304, 1, 0, 1198, 1.004040
49740996, 0,3506434498, 1, 0, 1194, 1.000687
49740997, 0,3506435691, 1, 0, 1193, 0.999849

Sleepを削除します。

50036130, 0,3858528396, 2, 0, 1196, 1.002363
50036130, 0,3858529591, 0, 0, 1195, 1.001525
50036131, 0,3858530787, 1, 0, 1196, 1.002363
50036132, 0,3858531982, 1, 0, 1195, 1.001525
50036133, 0,3858533178, 1, 0, 1196, 1.002363
50036134, 0,3858534375, 1, 0, 1197, 1.003202
50036136, 0,3858535570, 2, 0, 1195, 1.001525
50036136, 0,3858536764, 0, 0, 1194, 1.000687
50036137, 0,3858537959, 1, 0, 1195, 1.001525
50036138, 0,3858539154, 1, 0, 1195, 1.001525
50036139, 0,3858540349, 1, 0, 1195, 1.001525
50036140, 0,3858541544, 1, 0, 1195, 1.001525
50036142, 0,3858542737, 2, 0, 1193, 0.999849
50036142, 0,3858543932, 0, 0, 1195, 1.001525
50036143, 0,3858545129, 1, 0, 1197, 1.003202

以上の結果から、

ということがわかった。

 

結論