web-dev-qa-db-ja.com

Sleep(500)のコストが500msを超えるのはなぜですか?

コードでSleep(500)を使用し、getTickCount()を使用してタイミングをテストしました。 500を超える約515ミリ秒のコストがあることがわかりました。誰かがその理由を知っていますか?

53
kookoo121

Win32 APIのSleepは高精度のスリープではなく、最大の粒度を持っているためです。

正確なスリープを取得する最良の方法は、少しスリープ(約50ミリ秒)してビジー待機を行うことです。 busywaitに必要な正確な時間を見つけるには、timeGetDevCapsを使用してシステムクロックの解像度を取得し、安全のために1.5または2を掛けます。

63
orlp

sleep(500)は、少なくとも 500msのスリープを保証します。

しかし、それよりも長くスリープする可能性があります。上限は定義されていません。

あなたの場合、getTickCount()を呼び出す際に余分なオーバーヘッドもあります。

非標準のSleep関数は、別の問題で動作する可能性があります。しかし、正確さが保証されているとは思えません。そのためには、特別なハードウェアが必要です。

30
Bathsheba

ドキュメント で読むことができるように、WinAPI関数GetTickCount()

通常は10ミリ秒から16ミリ秒の範囲にあるシステムタイマーの解像度に制限されます。

より正確な時間測定値を取得するには、関数 GetSystemDatePreciseAsFileTime を使用します

また、Sleep(500)に依存してスリープexactly500ミリ秒にすることはできません。スレッドを少なくとも500ミリ秒中断します。オペレーティングシステムは、タイムスロットが使用可能になるとすぐにスレッドを続行します。オペレーティングシステムで他の多くのタスクが実行されている場合、遅延が発生する可能性があります。

18
Philipp

一般に、スリープとは、スレッドが待機状態になり、500ms後に「実行可能」状態になることを意味します。次に、OSスケジューラは、その時点で実行可能なプロセスの優先度と数に従ってsomethingを実行することを選択します。したがって、高精度のスリープと高精度のクロックを使用している場合は、正確に500ミリ秒ではなく、少なくとも500ミリ秒はスリープのままです。

13
Notinlist

他の回答が指摘したように、Sleep()の精度は限られています。実際には、noSleep()- like関数の実装は、いくつかの理由で完全に正確な場合があります。

  • Sleep()を実際に呼び出すには時間がかかります。最大限の精度を目指した実装couldは、このオーバーヘッドを測定して補正しようとしますが、気にする人はほとんどいません。 (そして、いずれにしても、オーバーヘッドはCPUやメモリの使用を含む多くの原因により変化する可能性があります。)

  • Sleep()で使用される基礎タイマーがexactly希望の時間に起動したとしても、起動直後にプロセスが実際に再スケジュールされるという保証はありません。プロセスがスリープ中にスワップアウトされたか、他のプロセスがCPUを占有している可能性があります。

  • OS cannotは、要求された時間にプロセスを起動する可能性があります。コンピューターがサスペンドモードになっているためです。このような場合、500ms Sleep()呼び出しが実際に数時間または数日かかる可能性があります。

また、たとえSleep()が完全に正確であったとしても、実行するコードafter sleepingは必然的に余分な時間を消費します。したがって、一定の間隔で何らかのアクション(画面の再描画、ゲームロジックの更新など)を実行するには、標準的な解決策としてcompensatedSleep()ループを使用します。つまり、次のアクションshouldがいつ発生するかを示す定期的に増加する時間カウンターを維持し、この目標時間を現在のシステム時間と比較して、睡眠時間を動的に調整します。

予期しない大規模なジャンプに対処するには、いくつかの特別な注意が必要です。コンピューターが一時的に疑われる場合、またはティックカウンターがラップアラウンドした場合、およびアクションの処理が次のアクションの前に利用可能な時間よりも長くかかり、ループが遅れる場合。

これらの問題の両方を処理する簡単な実装例(擬似コード)を次に示します。

_int interval = 500, giveUpThreshold = 10*interval;
int nextTarget = GetTickCount();

bool active = doAction();
while (active) {
    nextTarget += interval;
    int delta = nextTarget - GetTickCount();
    if (delta > giveUpThreshold || delta < -giveUpThreshold) {
        // either we're hopelessly behind schedule, or something
        // weird happened; either way, give up and reset the target
        nextTarget = GetTickCount();
    } else if (delta > 0) {
        Sleep(delta);
    }
    active = doAction();
}
_

これにより、少なくともそれ以上の時間を一貫して消費しない限り、doAction()平均intervalミリ秒ごとに1回呼び出されます。大きな時間ジャンプが発生しない限り。連続する呼び出し間の正確な時間は異なる場合がありますが、そのような変化は次の相互作用で補償されます。

12
Ilmari Karonen

デフォルトのタイマー解像度は低く、必要に応じて時間解像度を上げることができます。 [〜#〜] msdn [〜#〜]

#define TARGET_RESOLUTION 1         // 1-millisecond target resolution

TIMECAPS tc;
UINT     wTimerRes;

if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) != TIMERR_NOERROR) 
{
    // Error; application can't continue.
}

wTimerRes = min(max(tc.wPeriodMin, TARGET_RESOLUTION), tc.wPeriodMax);
timeBeginPeriod(wTimerRes); 
2
ISanych

コードが「スリープ」のような関数を必要とする一般的な理由は2つあります。

  1. 少なくとも将来的にはある程度の距離があるときにいつでも実行できるタスクがあります。

  2. これには、将来のある時点で可能な限り近く実行する必要のあるタスクがあります。

優れたシステムでは、これらの種類のリクエストを発行する別の方法が必要です。 Windowsは、最初のものを2番目のものよりも簡単にします。

システムに1つのCPUと3つのスレッドがあり、すべてが真夜中の1秒前までに有用な作業を行っており、スレッドの1つが少なくとも1秒間は役に立たないと言うと仮定します。その時点で、システムは残りの2つのスレッドに実行を割り当てます。真夜中の1ミリ秒前に、これらのスレッドの1つが少なくとも1秒間は何もする必要がないと判断した場合、システムは制御を最後の残りのスレッドに切り替えます。

真夜中にロールバックすると、元の最初のスレッドが実行可能になりますが、現在実行中のスレッドはその時点でミリ秒のCPUしか持っていないため、元の最初のスレッドがより「価値がある」と見なされる特別な理由はありませんちょうど制御を得た他のスレッドよりCPU時間の。スレッドの切り替えは無料ではないため、OSは、現在CPUを搭載しているスレッドが何かをブロックするか、タイムスライス全体を使い果たすまでCPUを保持する必要があると判断する可能性があります。

マルチメディアタイマーよりも使いやすいバージョンの「スリープ」があればよいかもしれませんが、システムがスレッドを再度実行する資格がある場合に一時的な優先度のブーストを与えるように要求するか、特定の時間枠内で実行する必要があるタスクに対して、最小時間と「優先度向上」時間を指定する「スリープ」。ただし、そのように簡単に動作させることができるシステムは知りません。

1
supercat