为什么睡眠(500)的成本超过500毫秒?

我在我的代码中使用了Sleep(500) ,并使用getTickCount()来testing计时。 我发现它的成本大约是515ms,超过了500个。有人知道这是为什么吗?

由于Win32 API的Sleep不是高精度睡眠,并且具有最大粒度。

获得精确睡眠的最好方法是睡眠时间稍短(约50毫秒),并忙于等待。 要查找需要等待的确切时间,请使用timeGetDevCaps获取系统时钟的分辨率,并乘以1.5或2以确保安全。

sleep(500)保证至less 500ms的睡眠。

但它可能会睡得更久:上限没有定义。

在你的情况下,调用getTickCount()会有额外的开销。

您的非标准Sleepfunction可能会有不同的performance; 但是我怀疑这个确切性是有保证的。 要做到这一点,你需要特殊的硬件。

正如你可以在文档中读到的 ,WinAPI函数GetTickCount()

限于系统定时器的分辨率,通常在10毫秒到16毫秒的范围内。

要获得更准确的时间测量,请使用函数GetSystemDatePreciseAsFileTime

此外,你不能依靠Sleep(500) 准确地睡500毫秒。 它会暂停线程至less 500毫秒。 只要有可用的时隙,操作系统就会继续线程。 在操作系统上运行许多其他任务时,可能会有延迟。

一般而言,睡眠意味着你的线程进入等待状态,在500ms之后它将处于“可运行”状态。 然后OS调度器根据当时的可运行进程的优先级和数量来select运行某些东西 。 所以,如果你有高精度的睡眠和高精度的时钟,那么它仍然是一个至less500毫秒的睡眠,而不是500毫秒。

与其他答案一样, Sleep()准确性有限。 实际上, 没有类似Sleep()的函数的实现是完全准确的,原因如下:

  • 实际调用Sleep()需要一些时间。 虽然旨在获得最大精度的实现可能会尝试测量和补偿这种开销,但很less有麻烦。 (而且,在任何情况下,由于许多原因,包括CPU和内存使用,开销可能会有所不同。)

  • 即使Sleep()使用的底层定时器正好在所需的时间触发,也不能保证您的进程在唤醒之后会立即被重新安排。 您的进程在hibernate时可能已被换出,或者其他进程可能占用CPU。

  • 操作系统可能无法在要求的时间唤醒您的进程,例如因为计算机处于挂起模式。 在这种情况下,您的500ms Sleep()调用很可能最终会花费几个小时甚至几天的时间。

此外,即使Sleep()是完全准确的,您想要睡眠运行的代码将不可避免地消耗一些额外的时间。 因此,要定期执行一些操作(例如重新绘制屏幕或更新游戏逻辑),标准解决scheme是使用补偿的 Sleep()循环。 也就是说,您需要维护一个有规律的递增时间计数器,以指示何时应该发生下一个动作,并将此目标时间与当前系统时间进行比较,以dynamic调整您的睡眠时间。

需要采取一些额外的措施来处理意外的大时间跳转,例如,如果计算机暂时被怀疑,或者如果打勾计数器缠绕,以及处理动作的情况最终花费的时间比下一个之前可用的时间多行动,导致循环落后。

下面是一个应该处理这两个问题的简单示例实现(伪代码):

 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几毫秒平均被调用一次,至less只要不会持续消耗更多的时间,并且只要不发生大的时间跳转。 连续通话之间的确切时间可能会有所不同,但任何此类变化将在下一次交易中得到补偿。

默认的定时器分辨率很低,如果需要,可以增加时间分辨率。 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); 

代码可能需要像“睡眠”这样的函数有两个一般原因:

  1. 它有一些任务可以在将来至less有一段距离的任何时间执行。

  2. 它有一些任务应该尽可能在将来某个时间点的某个时刻执行。

在一个好的系统中,应该有不同的方式来发出这种请求; Windows使第一个比第二个容易。

假设系统中有一个CPU和三个线程,所有这些工作都在做有用的工作,直到午夜前一秒,其中一个线程表示至less有一秒钟没有任何用处。 此时,系统将执行到剩下的两个线程。 如果在午夜前1ms,其中一个线程决定在至less一秒内没有任何用处,系统将把控制切换到最后剩下的线程。

午夜的时候,原来的第一个线程将可以运行,但是由于目前正在执行的线程在那个时候只有一个毫秒的CPU,所以没有特别的原因,原来的第一个线程应该被认为是“值得的”的CPU时间比刚刚得到控制的其他线程。 由于切换线程不是免费的,操作系统可能会很好地决定,目前有CPU的线程应该保留它,直到它阻塞某个东西或已经用完了整个时间片。

如果有一个比“多媒体定时器”更容易使用的“睡眠”版本,可能会更好,但会要求系统在线程有资格再次运行时给予线程暂时的优先级提升,或者更好地改变对于需要在特定时间窗口内执行的任务,“睡眠”将指定最小时间和“优先级提升”时间。 不过,我不知道有哪个系统可以很容易地以这种方式工作。