ちょっと待機時間を設けよう。処理の時間計測をしよう。
そんなことはちょいちょいありますよね。
しかし、そのタイマーがこっそり変な動きをしていることがあります。
それはその機能の精度が原因です。
ダメな例
Sleepという、いかにもなWindows APIがあります。
宣言は以下
void Sleep(
[in] DWORD dwMilliseconds
);
引数に待機時間をミリ秒単位で指定できます。
実験
Sleepが何ミリ秒スリープできているか、確認してみます。
for (int i = 0; i < 10; i++)
{
FILETIME startFileTime = { 0 };
::GetSystemTimePreciseAsFileTime(&startFileTime);
::Sleep(1);
FILETIME endFileTime = { 0 };
::GetSystemTimePreciseAsFileTime(&endFileTime);
ULONG64 startTime = startFileTime.dwLowDateTime + ((ULONG64)startFileTime.dwHighDateTime << 16);
ULONG64 endTime = endFileTime.dwLowDateTime + ((ULONG64)endFileTime.dwHighDateTime << 16);
printf("%llu\r\n", endTime - startTime);
}
10連続で、Sleepにかかった時間を計測して見ます。自分の環境では以下の結果となりました。
FileTimeは単位が100ナノ秒なので、ミリ秒に直すと4ms~15msがSleep(1);
にかかった時間ということになります。
回答
SleepのMSDNのページに回答はあります。
システムクロックが一定間隔で行われ、そのtickをSleep時間のチェック基準としています。tickの発生間隔が長いほど、その精度が悪くなるということです。
tickの頻度を上げるためにtimeBeginPeriodというWindows APIが提供されています。
実験
for (int i = 0; i < 10; i++)
{
::timeBeginPeriod(1);
FILETIME startFileTime = { 0 };
::GetSystemTimePreciseAsFileTime(&startFileTime);
::Sleep(1);
FILETIME endFileTime = { 0 };
::GetSystemTimePreciseAsFileTime(&endFileTime);
::timeEndPeriod(1);
ULONG64 startTime = startFileTime.dwLowDateTime + ((ULONG64)startFileTime.dwHighDateTime << 16);
ULONG64 endTime = endFileTime.dwLowDateTime + ((ULONG64)endFileTime.dwHighDateTime << 16);
printf("%llu\r\n", endTime - startTime);
}
1.5ms~1.9msになったので、大分マシになりましたね。
注意喚起
timeBeginPeriod
を使っておけば良いというわけではありません。
Sleepのページの注意書きにもある通り、システムクロック、システム電力使用量、スケジューラーに大きな影響を与えるためです。
おまけ
Sleep(0);
の場合は、タイマー精度を変更しなくても、ほぼ0msの待機になります。
Sleep(0);
は0ms待機という目的で使われるわけではなく、別スレッドにタイムスライスを譲るという意味合いだからです。
その他精度が低いやつ
-
GetTickCount64
- システムが開始されてから経過したミリ秒数を取得
-
timeGetTime
- システム時刻をミリ秒単位で取得
タイマーの結果がなんか変だな、と思った方の役に立てれば幸いです。
用途によって、使い分けて行きましょう。