Sleep(1)和SDL_Delay(1)需要15毫秒。

11
我正在编写一个C++/SDL/OpenGL应用程序,遇到了一个奇怪的错误。使用简单的变量时间步长时,游戏似乎运行正常。但是FPS开始表现异常。我发现Sleep(1)和SDL_Delay(1)函数在执行时都需要15毫秒的时间。在0-15之间的任何输入都需要15毫秒才能完成,将FPS锁定在大约64。如果将其设置为16,则需要30毫秒O.O。
我的循环看起来像这样:
while (1){
    GLuint t = SDL_GetTicks();
    Sleep(1); //or SDL_Delay(1)
    cout << SDL_GetTicks() - t << endl; //outputs 15
}

通常情况下,它不会像预期的那样只需要1毫秒,而是大多数情况下需要15毫秒。

我的操作系统是Windows 8.1。CPU是英特尔i7。我正在使用SDL2。


4
可能是WinAPI Sleep()函数调用的休眠时间比预期长的重复问题。 - danielschemmel
如果您期望线程/进程实时唤醒,就不要将其置于睡眠状态。如果您不想担心调度问题,请使用自旋锁。 - Andon M. Coleman
3个回答

15

默认的时钟频率为64Hz,即15.625ms/滴答。您需要使用timeBeginPeriod(1)将其更改为1000Hz == 1ms。相关MSDN文章:

http://msdn.microsoft.com/en-us/library/windows/desktop/dd757624(v=vs.85).aspx

如果目标是获得固定频率序列,则应使用更高分辨率的计时器,但不幸的是这些计时器只能进行轮询,因此需要使用轮询和休眠的组合以减少CPU开销。以下是示例代码,假设Sleep(1)最多可能需要近2毫秒(在Windows XP中会发生,但在后续版本中不会)。

/* code for a thread to run at fixed frequency */
#define FREQ 400                        /* frequency */

typedef unsigned long long UI64;        /* unsigned 64 bit int */

LARGE_INTEGER liPerfFreq;               /* used for frequency */
LARGE_INTEGER liPerfTemp;               /* used for query */
UI64 uFreq = FREQ;                      /* process frequency */
UI64 uOrig;                             /* original tick */
UI64 uWait;                             /* tick rate / freq */
UI64 uRem = 0;                          /* tick rate % freq */
UI64 uPrev;                             /* previous tick based on original tick */
UI64 uDelta;                            /* current tick - previous */
UI64 u2ms;                              /* 2ms of ticks */
#if 0                                   /* for optional error check */
static DWORD dwLateStep = 0;
#endif
    /* get frequency */
    QueryPerformanceFrequency(&liPerfFreq);
    u2ms = ((UI64)(liPerfFreq.QuadPart)+499) / ((UI64)500);

    /* wait for some event to start this thread code */
    timeBeginPeriod(1);                 /* set period to 1ms */
    Sleep(128);                         /* wait for it to stabilize */

    QueryPerformanceCounter((PLARGE_INTEGER)&liPerfTemp);
    uOrig = uPrev = liPerfTemp.QuadPart;

    while(1){
        /* update uWait and uRem based on uRem */
        uWait = ((UI64)(liPerfFreq.QuadPart) + uRem) / uFreq;
        uRem  = ((UI64)(liPerfFreq.QuadPart) + uRem) % uFreq;
        /* wait for uWait ticks */
        while(1){
            QueryPerformanceCounter((PLARGE_INTEGER)&liPerfTemp);
            uDelta = (UI64)(liPerfTemp.QuadPart - uPrev);
            if(uDelta >= uWait)
                break;
            if((uWait - uDelta) > u2ms)
                Sleep(1);
        }
        #if 0                    /* optional error check */
        if(uDelta >= (uWait*2))
            dwLateStep += 1;
        #endif
        uPrev += uWait;
        /* fixed frequency code goes here */
        /*  along with some type of break when done */
    }

    timeEndPeriod(1);                   /* restore period */

由于uPrev基于计算自原始计时器读数以来的滴答数,因此使用此方法随着时间的推移不会出现漂移,而不是依赖于当前和先前计时器读数之间的增量。由于睡眠设置允许最多2毫秒的延迟,所以示例代码应该适用于高达约400hz的情况。如果不需要Windows XP支持,则延迟可以假定Sleep(1)将花费约1毫秒(可能需要添加一些余地),在这种情况下,它应该适用于800hz。 - rcgldr
如果固定周期是1毫秒的精确倍数,并且不需要支持Windows XP,则可以使用多媒体计时器事件函数。MSDN文章:http://msdn.microsoft.com/en-us/library/windows/desktop/dd742877(v=vs.85).aspx。 Windows XP是一个问题,滴答声实际上将以1024hz运行,并且通过在42、42之后包括额外的滴答声,然后是41个滴答声,以便128个实际滴答声最终达到125个伪滴答声(以回到1ms边界)。 - rcgldr

1

看起来15毫秒是操作系统能够提供给你的最小时间片。我不确定你使用的具体框架,但通常sleep函数可以保证最小休眠时间(例如,它将至少休眠1毫秒)。


0

SDL_Delay()/Sleep()在10-15毫秒以下的时间内无法可靠使用。CPU时钟周期不足以快速检测1毫秒的差异。

请参阅此处的SDL文档


4
CPU时钟周期的计数足够快,真正的问题是当你让一个正在运行的线程休眠时会发生什么。它会进入就绪队列的末尾,然后就受调度的控制了。一些框架在间隔时间足够短的情况下能够避免这种行为,但显然这个框架并没有那么智能。或者,您可以将进程优先级设置为实时,这样该进程将频繁抢占其他进程,在典型的10-15毫秒时间片之前完成运行。 - Andon M. Coleman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接