为什么 Sleep(500) 花费超过 500 毫秒?
Why does Sleep(500) cost more than 500ms?
我在我的代码中使用了 Sleep(500)
并且我使用了 getTickCount()
来测试时间。我发现它有大约515ms的成本,超过500。有人知道这是为什么吗?
如您在 the documentation 中所读,WinAPI 函数 GetTickCount()
is limited to the resolution of the system timer, which is typically in the range of 10 milliseconds to 16 milliseconds.
要获得更准确的时间测量,请使用函数 GetSystemDatePreciseAsFileTime
此外,您不能依赖 Sleep(500)
睡眠正好 500 毫秒。它将暂停线程 至少 500 毫秒。一旦有可用的时隙,操作系统就会继续执行该线程。当操作系统上有很多其他任务运行时,可能会有延迟。
因为Win32API的Sleep
不是高精度休眠,有最大粒度
获得精确睡眠的最佳方法是少睡一点(~50 毫秒)并进行忙碌等待。要找到您需要 busywait 的确切时间量,请使用 timeGetDevCaps
获取系统时钟的分辨率并乘以 1.5 或 2 以确保安全。
sleep(500)
保证 至少 500 毫秒的睡眠。
但它可能会休眠更长的时间:上限未定义。
在您的情况下,调用 getTickCount()
.
也会产生额外的开销
您的非标准 Sleep
函数可能在不同的情况下表现良好;但我怀疑是否能保证准确性。为此,您需要特殊的硬件。
默认计时器分辨率较低,如有必要,您可以提高时间分辨率。 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);
一般来说,睡眠意味着您的线程进入等待状态,500 毫秒后它将处于 "runnable" 状态。然后OS调度器根据当时运行nable进程的优先级和数量选择运行something。因此,如果您确实拥有高精度睡眠和高精度时钟,那么它仍然是至少 500 毫秒的睡眠,而不是恰好 500 毫秒。
如其他答案所述,Sleep()
的准确性有限。实际上,没有 实现类似 Sleep()
的函数可以非常准确,原因如下:
实际调用Sleep()
需要一些时间。虽然旨在最大准确度的实现 可以 尝试测量和补偿这种开销,但很少有人会为此烦恼。 (而且,在任何情况下,开销都会因多种原因而有所不同,包括 CPU 和内存使用。)
即使 Sleep()
使用的基础计时器在 准确 所需时间触发,也不能保证您的进程实际上会立即重新安排醒来之后。您的进程可能在休眠时被换出,或者其他进程可能正在占用 CPU.
OS可能无法在请求的时间唤醒您的进程,例如因为计算机处于挂起模式。在这种情况下,您的 500 毫秒 Sleep()
调用很可能最终会花费数小时或数天。
此外,即使 Sleep()
完全准确,您要 运行 睡眠后的代码也不可避免地会消耗一些额外的时间。
因此,要定期执行某些操作(例如重绘屏幕或更新游戏逻辑),标准解决方案是使用 compensated Sleep()
循环。也就是说,您维护一个定期递增的时间计数器,指示下一个动作应该何时发生,并将此目标时间与当前系统时间进行比较以动态调整您的睡眠时间。
需要特别注意处理意外的大时间跳跃,例如如果计算机暂时被怀疑或滴答计数器环绕,以及处理动作最终花费的时间比下一个动作之前可用的时间更多的情况,导致循环滞后。
下面是一个可以处理这两个问题的快速示例实现(伪代码):
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
毫秒 调用一次 ,至少只要它不会持续消耗更多时间比那个,只要没有大的时间跳跃发生。连续调用之间的确切时间可能会有所不同,但任何此类变化都会在下一次交互时得到补偿。
代码可能需要像 "sleep" 这样的函数的一般原因有两个:
它有一些任务可以在未来至少一段距离的任何时间执行。
它有一些任务应该在未来某个距离的某个时刻尽可能近地执行。
在一个好的系统中,应该有不同的方式来发出这些类型的请求; Windows 使第一个比第二个更容易。
假设系统中有一个CPU和三个线程,都在做有用的事情
工作到午夜前一秒,其中一个线程说它不会
至少一秒钟做任何有用的事情。届时,系统将
专门执行剩余的两个线程。如果,午夜前 1 毫秒,
其中一个线程决定它至少在
一秒钟,系统将控制切换到最后一个剩余的线程。
午夜时分,原来的第一个线程将可用
运行,但由于当前正在执行的线程只有 CPU
那时一毫秒,没有特别的原因原来的第一个
线程应该被认为比另一个线程多 "worthy" 的 CPU 时间
刚刚得到控制。由于切换线程不是免费的,OS 可能非常
我们决定当前拥有 CPU 的线程应该保留它直到
它阻塞了某些东西或者已经用完了整个时间片。
如果 "sleep" 有一个更好用的版本就好了
比多媒体计时器,但会要求系统给线程一个
当它有资格再次 运行 或更好时临时提升优先级
"sleep" 的变体,它将指定最短时间和“优先级-
提升”时间,用于需要在特定时间内执行的任务 window。不过,我不知道有任何系统可以轻松地以这种方式工作。
我在我的代码中使用了 Sleep(500)
并且我使用了 getTickCount()
来测试时间。我发现它有大约515ms的成本,超过500。有人知道这是为什么吗?
如您在 the documentation 中所读,WinAPI 函数 GetTickCount()
is limited to the resolution of the system timer, which is typically in the range of 10 milliseconds to 16 milliseconds.
要获得更准确的时间测量,请使用函数 GetSystemDatePreciseAsFileTime
此外,您不能依赖 Sleep(500)
睡眠正好 500 毫秒。它将暂停线程 至少 500 毫秒。一旦有可用的时隙,操作系统就会继续执行该线程。当操作系统上有很多其他任务运行时,可能会有延迟。
因为Win32API的Sleep
不是高精度休眠,有最大粒度
获得精确睡眠的最佳方法是少睡一点(~50 毫秒)并进行忙碌等待。要找到您需要 busywait 的确切时间量,请使用 timeGetDevCaps
获取系统时钟的分辨率并乘以 1.5 或 2 以确保安全。
sleep(500)
保证 至少 500 毫秒的睡眠。
但它可能会休眠更长的时间:上限未定义。
在您的情况下,调用 getTickCount()
.
您的非标准 Sleep
函数可能在不同的情况下表现良好;但我怀疑是否能保证准确性。为此,您需要特殊的硬件。
默认计时器分辨率较低,如有必要,您可以提高时间分辨率。 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);
一般来说,睡眠意味着您的线程进入等待状态,500 毫秒后它将处于 "runnable" 状态。然后OS调度器根据当时运行nable进程的优先级和数量选择运行something。因此,如果您确实拥有高精度睡眠和高精度时钟,那么它仍然是至少 500 毫秒的睡眠,而不是恰好 500 毫秒。
如其他答案所述,Sleep()
的准确性有限。实际上,没有 实现类似 Sleep()
的函数可以非常准确,原因如下:
实际调用
Sleep()
需要一些时间。虽然旨在最大准确度的实现 可以 尝试测量和补偿这种开销,但很少有人会为此烦恼。 (而且,在任何情况下,开销都会因多种原因而有所不同,包括 CPU 和内存使用。)即使
Sleep()
使用的基础计时器在 准确 所需时间触发,也不能保证您的进程实际上会立即重新安排醒来之后。您的进程可能在休眠时被换出,或者其他进程可能正在占用 CPU.OS可能无法在请求的时间唤醒您的进程,例如因为计算机处于挂起模式。在这种情况下,您的 500 毫秒
Sleep()
调用很可能最终会花费数小时或数天。
此外,即使 Sleep()
完全准确,您要 运行 睡眠后的代码也不可避免地会消耗一些额外的时间。
因此,要定期执行某些操作(例如重绘屏幕或更新游戏逻辑),标准解决方案是使用 compensated Sleep()
循环。也就是说,您维护一个定期递增的时间计数器,指示下一个动作应该何时发生,并将此目标时间与当前系统时间进行比较以动态调整您的睡眠时间。
需要特别注意处理意外的大时间跳跃,例如如果计算机暂时被怀疑或滴答计数器环绕,以及处理动作最终花费的时间比下一个动作之前可用的时间更多的情况,导致循环滞后。
下面是一个可以处理这两个问题的快速示例实现(伪代码):
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
毫秒 调用一次 ,至少只要它不会持续消耗更多时间比那个,只要没有大的时间跳跃发生。连续调用之间的确切时间可能会有所不同,但任何此类变化都会在下一次交互时得到补偿。
代码可能需要像 "sleep" 这样的函数的一般原因有两个:
它有一些任务可以在未来至少一段距离的任何时间执行。
它有一些任务应该在未来某个距离的某个时刻尽可能近地执行。
在一个好的系统中,应该有不同的方式来发出这些类型的请求; Windows 使第一个比第二个更容易。
假设系统中有一个CPU和三个线程,都在做有用的事情 工作到午夜前一秒,其中一个线程说它不会 至少一秒钟做任何有用的事情。届时,系统将 专门执行剩余的两个线程。如果,午夜前 1 毫秒, 其中一个线程决定它至少在 一秒钟,系统将控制切换到最后一个剩余的线程。
午夜时分,原来的第一个线程将可用 运行,但由于当前正在执行的线程只有 CPU 那时一毫秒,没有特别的原因原来的第一个 线程应该被认为比另一个线程多 "worthy" 的 CPU 时间 刚刚得到控制。由于切换线程不是免费的,OS 可能非常 我们决定当前拥有 CPU 的线程应该保留它直到 它阻塞了某些东西或者已经用完了整个时间片。
如果 "sleep" 有一个更好用的版本就好了 比多媒体计时器,但会要求系统给线程一个 当它有资格再次 运行 或更好时临时提升优先级 "sleep" 的变体,它将指定最短时间和“优先级- 提升”时间,用于需要在特定时间内执行的任务 window。不过,我不知道有任何系统可以轻松地以这种方式工作。