统一调节程序执行率 [视窗C++]

Uniformly Regulating Program Execution Rate [Windows C++]

本文关键字:视窗 C++ 执行率 调节 程序      更新时间:2023-10-16

首先,我找到了很多关于这个主题的信息,但不幸的是,没有解决问题的解决方案。

我只是试图将我的C++程序调节为每秒 60 次迭代运行。我已经尝试了从GetClockTicks((到GetLocalTime((的所有方法来帮助监管,但是每次我在Windows Server 2008机器上运行该程序时,它的运行速度都比在本地机器上慢,我不知道为什么!

我知道基于"时钟"的函数调用返回执行时花费的 CPU 时间,所以我去了 GetLocalTime,然后尝试区分开始时间和停止时间,然后调用 Sleep((FPS/1000( - 毫秒执行时间(

我的本地机器比服务器CPU快得多,所以很明显,人们的想法是它正在关闭CPU时钟周期,但这并不能解释为什么GetLocalTime不起作用。我一直在基于 http://www.lazyfoo.net/SDL_tutorials/lesson14/index.php 更改 get_ticks(( 的方法,并返回我可以在网络上找到的所有函数。

例如,采用以下代码:

#include <Windows.h>
#include <time.h>
#include <string>
#include <iostream>
using namespace std;
int main() {
    int tFps = 60;
    int counter = 0;
    SYSTEMTIME gStart, gEnd, start_time, end_time;
    GetLocalTime( &gStart );
    bool done = false;
    while(!done) {
        GetLocalTime( &start_time );
        Sleep(10);
        counter++;
        GetLocalTime( &end_time );
        int startTimeMilli = (start_time.wSecond * 1000 + start_time.wMilliseconds);
        int endTimeMilli = (end_time.wSecond * 1000 + end_time.wMilliseconds);
        int time_to_sleep = (1000 / tFps) - (endTimeMilli - startTimeMilli);

        if (counter > 240)
            done = true;
        if (time_to_sleep > 0)
            Sleep(time_to_sleep);
    }
    GetLocalTime( &gEnd );
    cout << "Total Time: " << (gEnd.wSecond*1000 + gEnd.wMilliseconds) - (gStart.wSecond*1000 + gStart.wMilliseconds) << endl;
    cin.get();
}

对于此代码片段,在我的计算机 (3.06 GHz( 上运行,我得到的总时间 (ms( 为 3856,而在我的服务器 (2.53 GHz( 上,我得到 6256。因此,尽管 2.53/3.06 的比率仅为 .826797386,而 3856/6271 的比率为 .614893956,但它可能是处理器的速度。

我无法判断 Sleep 函数是否正在做与预期截然不同的事情,尽管我不明白为什么会这样,或者这是否是我获取时间的方法(即使它应该是世界时间 (ms( 而不是时钟周期时间。任何帮助将不胜感激,谢谢。

首先,Sleep 的默认分辨率是计算机的配额长度 - 通常为 10 毫秒或 15 毫秒,具体取决于 Windows 版本。要获得 1ms 的分辨率,您必须发出 timeBeginPeriod(1(,它将计时器硬件重新编程为每毫秒(大约(触发一次。

在你的主循环中,你可以

int main() 
{
    // Timers
    LONGLONG curTime = NULL;
    LONGLONG nextTime = NULL;
    Timers::GameClock::GetInstance()->GetTime(&nextTime);
    while (true) {    
        Timers::GameClock::GetInstance()->GetTime(&curTime);
        if ( curTime > nextTime  && loops <= MAX_FRAMESKIP ) { 
            nextTime += Timers::GameClock::GetInstance()->timeCount;
            // Business logic goes here and occurr based on the specified framerate
        }
    }
}

使用此时间库

include "stdafx.h"
LONGLONG cacheTime;
Timers::SWGameClock* Timers::SWGameClock::pInstance = NULL;
Timers::SWGameClock* Timers::SWGameClock::GetInstance ( ) { 
    if (pInstance == NULL) { 
        pInstance = new SWGameClock();
    }
    return pInstance;
}

Timers::SWGameClock::SWGameClock(void) {
    this->Initialize ( );
}
void Timers::SWGameClock::GetTime ( LONGLONG * t ) { 
    // Use timeGetTime() if queryperformancecounter is not supported 
    if (!QueryPerformanceCounter( (LARGE_INTEGER *) t)) { 
        *t = timeGetTime();
    }
    cacheTime = *t;
}
LONGLONG Timers::SWGameClock::GetTimeElapsed ( void ) { 
    LONGLONG t; 
    // Use timeGetTime() if queryperformancecounter is not supported
    if (!QueryPerformanceCounter( (LARGE_INTEGER *) &t )) { 
        t = timeGetTime();
    }
    return (t - cacheTime);
}
void Timers::SWGameClock::Initialize ( void ) { 
    if ( !QueryPerformanceFrequency((LARGE_INTEGER *) &this->frequency) ) { 
        this->frequency = 1000; // 1000ms to one second 
    }
    this->timeCount = DWORD(this->frequency / TICKS_PER_SECOND);
}
Timers::SWGameClock::~SWGameClock(void)
{
}

带有包含以下内容的头文件:

// Required for rendering stuff on time
#pragma once
#define TICKS_PER_SECOND 60
#define MAX_FRAMESKIP 5
namespace Timers { 
    class SWGameClock
    {
    public:
        static SWGameClock* GetInstance();
        void Initialize ( void );
        DWORD timeCount;
        void GetTime ( LONGLONG* t );
        LONGLONG GetTimeElapsed ( void );
        LONGLONG frequency; 
        ~SWGameClock(void);
    protected:
        SWGameClock(void);
    private:
        static SWGameClock* pInstance;
    }; // SWGameClock
} // Timers

这将确保您的代码以 60FPS(或您输入的任何内容(运行,尽管您可能会转储MAX_FRAMESKIP因为在此示例中没有真正实现!

您可以尝试使用 WinMain 函数并使用 SetTimer 函数和常规消息循环(您还可以利用 GetMessage( ... ) 的过滤机制(,其中您可以使用请求的时间测试WM_TIMER消息,当计数器达到限制时,执行PostQuitMessage(0)终止消息循环。

对于如此快的占空比,您可以使用高精度计时器(如 QueryPerformanceTimer(和忙碌等待循环。

如果你的占空比要低得多,但仍然想要精度,那么你可以睡一部分时间,然后用忙碌的等待循环来消耗剩余的时间。

另一种选择是使用类似DirectX的东西将自己同步到VSync中断(几乎总是60 Hz(。 如果您正在编写游戏或 A/V 演示文稿,这可能很有意义。

Windows不是一个实时操作系统,所以永远不会有完美的方法来做这样的事情,因为不能保证你的线程会被安排在你需要的时候运行。

请注意,在 Sleep 的备注中,实际时间量将至少是一个"滴答",并且可能比您在计划线程再次运行之前请求的延迟长一个完整的"滴答"(然后我们必须假设线程已调度(。 "勾号"可能会有很大差异,具体取决于硬件和Windows版本。 它通常在 10-15 毫秒的范围内,我见过它和 19 毫秒一样糟糕。 对于 60 Hz,每次迭代需要 16.666 毫秒,因此这显然不够精确,无法满足您的需求。

根据每帧渲染之间经过的时间进行渲染(迭代(怎么样?考虑创建一个void render(double timePassed)函数并根据timePassed参数进行呈现,而不是让程序进入睡眠状态。

例如,想象一下,你想渲染一个球掉落或弹跳。你会知道它是速度,加速度和你需要的所有其他物理。根据timePassed和所有其他物理参数(速度、加速度等(计算球的位置。

或者,如果您愿意,如果经过的时间值很小,则可以跳过render()函数执行,而不是将程序置于睡眠状态。