使用 std::chrono 库调整应用程序 fps 但出现奇怪的行为

Using std::chrono library to adjust the application fps but getting weird behavior

我使用 std::chrono c++ 库编写了下面的代码,我想做的是 修复应用程序的 FPSon 60,但我得到 50 FPS,这肯定不是性能问题 因为我什么都没有计算。但这肯定是无效用法或错误。

TARGET_FPS 宏设置为目标 FPS 我想要获取,然后控制台 window 显示真实的实际 FPS ,以下这些行显示我设置的值 TARGET_FPSto ,并且每个都与最终的 FPS.

相关联
 TARGET_FPS---->FPS

       60----->50
       90----->50
       100----->100
     1000----->100
     10000----->100
   whatever ----->100

即使我将 TARGET_FPS 定义为 1000000000,我也会得到 100 FPS,即使我将其定义为 458 或任何大于 100 的值,我也会得到 100 FPS 作为输出。

#include <chrono> /// to use std::chrono namespace 
#include <iostream> /// for console output
#include <thread> /// for std::this_thread::sleep_for()
#define TARGET_FPS 60// our target FPS    
using frame_len_type = std::chrono::duration<float,std::ratio<1,TARGET_FPS>>; /// this is the     duration that defines the length of a frame
using fsecond = std::chrono::duration<float>; /// this duration    represents once second and uses 'float' type as internal representation
const frame_len_type target_frame_len(1); /// we will define this    constant here , to represent on frame duration ( defined to avoid    construction inside a loop )
void app_logic(){ /** ... All application logic goes here ... **/}
int main() /// our main function !
{
    using sys_clock = std::chrono::system_clock; /// simplify the type    name to make the code readable
     sys_clock::time_point frame_begin,frame_end; /// we will use these time points to point to frame begin and end
     while (true)
     {
      frame_begin = sys_clock::now(); /// there we go !
      app_logic(); /// lets be logical here :)
      frame_end = sys_clock::now(); /// we are done so quick !
      std::this_thread::sleep_for( target_frame_len-    (frame_end.time_since_epoch()-frame_begin.time_since_epoch()) ); /// we will take a rest that is equal to what we where supposed to take to finish the actual target frame length
      std::cout<< fsecond(1) / ( sys_clock::now() - frame_begin) <<std::endl; /// this will show ass the current FPS
     }
    return 0; /// return to OS
} /// end of code

std::chrono 的时间分辨率取决于系统:

  • this answer 另一个问题中,您会找到一个代码片段来确定您的平台的大致时间分辨率。
  • 上 windows 7, default timer resolution is 15.6 ms
  • 另外,c++标准库不得不依赖的windows API sleep,不保证线程在等待时间过后立即恢复执行:

After the sleep interval has passed, the thread is ready to run. If you specify 0 milliseconds, the thread will relinquish the remainder of its time slice but remain ready. Note that a ready thread is not guaranteed to run immediately. Consequently, the thread may not run until some time after the sleep interval elapses.

  • C++ 标准库没有为 sleep_for 提供更好的保证,无论您使用什么 OS:

30.3.2/7: Effect: Blocks the calling thread for the relative timeout (...)

后果:

  • FPS 设置为 60 时,每 16.6 毫秒会有一帧。因此,假设您的 app_logic() 超快,您的线程将至少休眠 15.6 毫秒。如果逻辑需要 1 毫秒来执行,那么您的帧率正好是 60 FPS。
  • 但是,根据 API 文档,如果 [wait time] 大于 1 tick 但小于 2,则等待可以介于 1 和两个 ticks,因此平均睡眠时间将在 15.6 到 31.2 毫秒之间,这反过来意味着您的 FPS 将在 60 到 32 FPS 之间。这就解释了为什么你只能达到 50 FPS。

  • 当你设置FPS为100时,每10ms应该有一帧。这低于定时器精度。可能根本就睡不着。如果没有其他线程准备好 运行,该函数将立即 return,以便您达到最大吞吐量。如果您设置更高的 FPS,您将处于完全相同的情况,因为预期等待时间始终低于计时器精度。因此结果不会改善。

问题已解决:)

    #include <chrono> /// to use std::chrono namespace
    #include <iostream> /// for console output
    #include <thread> /// for std::this_thread::sleep_for()
    #include <windows.h>
    #define TARGET_FPS 500 /// our target fps as a macro
    const float target_fps = (float)TARGET_FPS; /// our target fps
    float tmp_target_fps = target_fps;  /// used to adjust the target fps depending on the actual real fps to reach the real target fps
    using frame_len_type = std::chrono::duration<float,std::ratio<1,TARGET_FPS>>; /// this is the     duration that defines the length of a frame
    using fsecond = std::chrono::duration<float>; /// this duration    represents once second and uses 'float' type as internal representation
    fsecond target_frame_len(1.0f/tmp_target_fps); /// we will define this    constant here , to represent on frame duration ( defined to avoid    construction inside a loop )
    bool enable_fps_oscillation = true;
    void app_logic()
    {
        /** ... All application logic goes here ... **/
    }
    class HeighResolutionClockKeeper
    {
    private :
        bool using_higher_res_timer;
    public :
        HeighResolutionClockKeeper() : using_higher_res_timer(false) {}
        void QueryHeighResolutionClock()
        {
            if (timeBeginPeriod(1) != TIMERR_NOCANDO)
            {
                using_higher_res_timer = true;
            }
        }
        void FreeHeighResolutionClock()
        {
            if (using_higher_res_timer)
            {
                timeEndPeriod(1);
            }
        }
        ~HeighResolutionClockKeeper()
        {
            FreeHeighResolutionClock(); /// if exception is thrown , if not this wont cause problems thanks to the flag we put
        }
    };
    int main() /// our main function !
    {
        HeighResolutionClockKeeper MyHeighResolutionClockKeeper;
        MyHeighResolutionClockKeeper.QueryHeighResolutionClock();
        using sys_clock = std::chrono::system_clock; /// simplify the type    name to make the code readable
        sys_clock::time_point frame_begin,frame_end; /// we will use these time points to point to frame begin and end
        sys_clock::time_point start_point = sys_clock::now();
        float accum_fps = 0.0f;
        int frames_count = 0;
        while (true)
        {
            frame_begin = sys_clock::now(); /// there we go !
            app_logic(); /// lets be logical here :)
            frame_end = sys_clock::now(); /// we are done so quick !
            std::this_thread::sleep_for( target_frame_len-    (frame_end.time_since_epoch()-frame_begin.time_since_epoch()) ); /// we will take a rest that is equal to what we where supposed to take to finish the actual target frame length
            float fps =  fsecond(1) / ( sys_clock::now() - frame_begin) ; /// this will show ass the current FPS

    /// obviously we will not be able to hit the exact FPS  we want se we need to oscillate around until we
    /// get a very close average FPS by time .
            if (fps < target_fps) /// our real fps is less than what we want
                tmp_target_fps += 0.01; /// lets ask for more !
            else if (fps > target_fps ) /// it is more than what we want
                tmp_target_fps -=0.01; /// lets ask for less
            if(enable_fps_oscillation == true)
            {
             /// now we will adjust our target frame length for match the new target FPS
                target_frame_len = fsecond(1.0f/tmp_target_fps);
           /// used to calculate average FPS
                accum_fps+=fps;
                frames_count++;
                /// each 1 second
                if( (sys_clock::now()-start_point)>fsecond(1.0f)) /// show average each 1 sec
                {
                    start_point=sys_clock::now();
                    std::cout<<accum_fps/frames_count<<std::endl; /// it is getting more close each time to our target FPS
                }
            }
            else
            {
                /// each frame
                std::cout<<fps<<std::endl;
            }
        }
        MyHeighResolutionClockKeeper.FreeHeighResolutionClock();
        return 0; /// return to OS
    } /// end of code

我必须添加 timeBeginPeriod() and timeEndPeriod() on windows platform , thanks to this awesome , lost-in-the-wind website http://www.geisswerks.com/ryan/FAQS/timing.html from Ryan Geiss

详情:

因为我们实际上无法达到我们想要的确切 fps(略高于或低于 1000 fps,由于 timeXPeriod(1) 而降至 1 fps)因此我使用了一些额外的 dump fps 变量调整我正在寻找的目标 fps,增加它和减少它..,这将使我们能够控制实际应用程序 fps 以平均达到我们的实际目标 fps(您可以使用 'enable_fps_oscillation' 标志启用和禁用它)这解决了 fps = 60 的问题,因为我们无法击中它 (+/-0.5),但如果我们设置 fps = 500,我们就击中了它,我们不需要在它下方和上方振荡