【问题标题】:Sleep(1) and SDL_Delay(1) takes 15 msSleep(1) 和 SDL_Delay(1) 需要 15 ms
【发布时间】:2014-06-09 03:12:03
【问题描述】:

我正在编写一个 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是intel i7。我正在使用 SDL2。

【问题讨论】:

标签: c++ opengl sdl


【解决方案1】:

代码默认为 64 赫兹,或 15.625 毫秒/滴答声。您需要使用 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 会发生这种情况,但更高版本的 Windows 不会发生这种情况)。

/* 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 ms 延迟,因此示例代码应该适用于最多约 400hz。如果不需要 Windows XP 支持,则延迟可以假定 Sleep(1) 将花费大约 1 毫秒(可能需要为此添加一些余量),在这种情况下,它应该适合 800hz。
  • 如果固定周期是1ms的精确倍数,并且不需要Windows XP支持,那么可以使用多媒体定时器事件函数。 MSDN 文章:msdn.microsoft.com/en-us/library/windows/desktop/…。 Windows XP 是一个问题,该代码实际上将以 1024hz 运行,而 1000hz 是通过在 42、42 和 41 个滴答声之后包含一个额外滴答声来模拟的,因此 128 个实际滴答声以 125 个伪滴答声结束(回到 1ms 边界) .
【解决方案2】:

看起来 15 毫秒是操作系统将提供给您的最小切片。我不确定您的具体框架,但 sleep 通常可以保证最少的睡眠时间。 (即它会休眠至少 1 毫秒。)

【讨论】:

    【解决方案3】:

    SDL_Delay()/Sleep() 不能在低于 10-15 毫秒的时间内可靠地使用。 CPU 滴答的注册速度不够快,无法检测到 1 毫秒的差异。

    请参阅SDL docs here

    【讨论】:

    • CPU 滴答声注册速度足够快,真正的问题是当您将正在运行的线程置于睡眠状态时会发生什么。它进入就绪队列的后面,然后受调度的支配。如果间隔足够小,一些框架足够聪明,可以避免这种行为,但显然这不是其中之一。或者,您可以将进程优先级设置为实时,该进程将经常在典型的 10-15 毫秒时间间隔之前抢占其他进程。
    猜你喜欢
    • 1970-01-01
    • 2016-06-09
    • 2021-11-11
    • 2020-03-09
    • 2018-08-27
    • 1970-01-01
    • 1970-01-01
    • 2012-07-06
    • 2011-10-21
    相关资源
    最近更新 更多