Sleep関数は指定の時間だけ停止するためのものですが、ここでは精度の問題について説明します。
なお、この精度の問題はウィンドウメッセージ処理や、あとで登場する時間取得関数にも影響を与えるため、
Windowsでゲームを作る場合に必要な知識として覚えて置いてください。
Sleep関数は引数にミリ秒を指定して呼び出すことで現在のスレッドをその間停止することが出来ますが、
実際には指定した引数の時間以上が停止するのであって、指定の時間ぴったり停止するものではありません。
実はSleepやウィンドウメッセージの処理間隔、時間取得関数などは全てCPUの割り込み処理にて処理されています。
割り込み処理とは例えば、外部のハードウェアなどからCPUにデータを受け取ってもらいたい場合に、
現在の処理を中断させてデータの受け取りを優先してやってもらうような場合に使用します。
しかし割り込みが頻繁に発生するということはCPUがそちらに多く取られることになり、
結果的に全体の処理が遅くなってしまいます。
Windowsでは時間のカウントに割り込みを使っていますが、初期状態ではこの割り込み時間が長めに設定されています。
例えばこの割り込みの間隔が10msだったとすると、割り込み内では時間カウンタを+10msずつ加算させることで、
現在の時間を誤差10ms以内として取得することが出来るわけです。
そしてSleep関数はこの精度でのタイミングで抜けてくるため、例えば上記のように初期設定が10msとなっているPCでは、
Sleepにそれ未満の時間を設定したとしても、結果的に10ms以上経過しなければ関数から抜けてこないということになります。
これがSleepの精度の問題ということになりますが、Windowsにはこれを最低の1msに設定するための関数が用意されています。
ただし、この場合は1秒間に1000回の割り込みが発生することになるため、10msだった時より10倍システムの負荷が大きくなります。
※最近のCPUならば大した負荷には感じませんが問題は電力使用量が増えるため、ノートやタブレットなどではバッテリーの減りが激しくなります
Windowsの割り込みタイミングを変えることでSleepなどの精度を上げることができます。
これには以下の関数を使います。
MMRESULT timeBeginPeriod( UINT uPeriod ); | |
アプリケーションまたはデバイスドライバの最小タイマ分解能を設定します。 uPeriodには最小タイマ分解能をミリ秒単位で指定します。 なお、設定できなかった場合はTIMERR_NOERRORが返りますが、 今どきのPCで1msを設定出来ないようなものは皆無なので、特にエラーチェックをしなくても問題ありません。 |
|
MMRESULT timeEndPeriod( UINT uPeriod ); | |
以前にセットされた最小タイマ分解能をクリアします。 uPeriodにはtimeBeginPeriod 関数で指定した最小タイマ分解能を指定します。 |
これらの関数には注意点があり、まずこの関数はCPUに直接作用するためOS全体の分解能に影響します。
例えば他のアプリがこの関数で精度を上げていた場合、何もしなくても自分のアプリも精度が上がりますが、
自分のアプリで精度を上げつつ他のアプリが精度を下げていた場合、一番精度の高い値でOSは動作するため、
基本的には自分のアプリ用に精度を上げておくだけで、他のアプリの精度は気にする必要はありません。
次にこれらの関数は必ず対で使用しなければなりません。
実は自分のアプリ内でも何回も呼び出すことが出来ますが、
精度を上げた場合は必ずその時に指定した値で元に戻す必要があります。
※誤って精度を上げたままアプリを終了してしまうと、その後OSの負荷が高いままになってしまうことがあります
以下はこの関数の使用例です。
int WINAPI WinMain( HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszCmdParam,int nCmdShow )
{
:
// 精度を1msにする
timeBeginPeriod( 1 );
// メインループ
while(1) {
:
Sleep(1); // 1msの精度で停止
}
// 精度を元に戻す
timeEndPeriod( 1 );
return 0;
}
1msの指定をしたとしてもあくまでも最低1msは停止することを保障しているのであって、
OSやCPU負荷によってはそれ以上停止してしまうことがあるので、
いずれにせよSleepの時間を信用してはなりません。