std::this_thread::sleep_until 时间完全偏离了大约 2 倍,莫名其妙

std::this_thread::sleep_until timing is completely off by about a factor of 2, inexplicably

好吧,我真的不知道为什么会这样。我目前正在实现一个线程容器,它以分离的方式运行无限循环,限制在每次迭代之间的特定速度。

Header:

class timeloop
{
public:
    std::thread thread = { };
    bool state = true;

    void (*function_pointer)() = nullptr;

    double ratio = 1.0f;
    std::chrono::nanoseconds elapsed = { };

    timeloop(
        void (*function_pointer)() = nullptr
    );

    void function();
};

定义:

void timeloop::start()
{
    this->thread = std::thread(
        &loop::function,
        this
    );
}

void timeloop::function()
{
    std::chrono::steady_clock::time_point next;

    std::chrono::steady_clock::time_point start;
    std::chrono::steady_clock::time_point end;

    while (
        this->state
        )
    {
        start = std::chrono::high_resolution_clock::now();
        next = start + std::chrono::nanoseconds(
            (long long) (this->ratio * (double) std::chrono::nanoseconds::period::den)
        );

        if (
            this->function_pointer != nullptr
            )
        {
            this->function_pointer();
        }

        /***************************
            this is the culprit
        ***************************/
        std::this_thread::sleep_until(
            next
        );

        end = std::chrono::high_resolution_clock::now();
        this->elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(
            end - start
            );
    }
}

调用代码:

timeloop* thread_draw = new timeloop(
    &some_void_function
);
thread_draw->ratio = 1.0 / 128.0;

thread_draw->start();
thread_draw->thread.detach();

定义代码行为异常,特别是 std::this_thread::sleep_until。对于 this->ratio = 1.0 / 128.0,我预计帧速率约为 128,startnext 的计算值强化了这一点,但它莫名其妙地徘徊在 60 左右。是的,我尝试只除以 next 下降了 2,但这实际上使它下降到 40 左右。

验证正常睡眠时间的额外代码:

auto diff = std::chrono::nanoseconds(
    next - start
).count() / (double) std::chrono::nanoseconds::period::den;
auto equal = diff == this->ratio;

其中 equal 的计算结果为 true

帧率计算:

double time = (double) thread_draw->elapsed.count() / (double) std::chrono::nanoseconds::period::den;
double fps = 1.0 / time;

虽然我也使用外部 FPS 计数器来验证(NVIDIA ShadowPlay 和 RivaTuner/MSI Afterburner),但它们在计算值的大约 +-5 范围内。

而且我知道它是 std::this_thread::sleep_until 因为一旦我注释掉它,帧率就会跳到 2000 左右。是的...

我真的对此感到困惑,尤其是看到我找不到任何其他人遇到过这个问题的证据。是的,我知道睡眠功能并不完全准确,而且时不时会打嗝,但持续睡眠几乎是预定时间的两倍是荒谬的。

我是不是配置错了编译器选项之类的?这绝对不是性能问题,而且我有理由相信这也不是逻辑错误(查看所有计算结果)[除非我在某处滥用 chrono]。

不保证sleep_until的分辨率,只保证线程不会在时间点之前被唤醒。如果您要实现主游戏循环,请阅读 Fix your timestep

使用睡眠来保证计时是一种糟糕的方法。你受制于 OS 调度程序,例如我相信 Windows 的最小睡眠时间约为 10 毫秒。 (如果实现实际上要求 OS 将线程置于休眠状态并且 OS 决定进行上下文切换。)

如果您调用 glfwSwapBuffers 或类似方法,延迟也可能是由绘图线程中的 VSync 引起的。这可以解释为什么你被限制为 60FPS,但不能解释为什么评论 sleep 可以解决问题。

所以我猜 OS 在上面睡觉。我建议取消睡眠并依赖 VSync,无论如何,这是您想要绘制的正确频率。与逻辑线程同步会很痛苦……但情况总是如此。

我认为你的问题是你没有使用 absolute 时间,因为你一直在重置你的 absolute 时间点 next relative到当前时间 now().

尝试改变这个:

while (
    this->state
    )
{
    start = std::chrono::high_resolution_clock::now();
    next = start + std::chrono::nanoseconds(
        (long long) (this->ratio * (double) std::chrono::nanoseconds::period::den)
    );
// ...

至此

// before the loop
next = std::chrono::high_resolution_clock::now();

while (
    this->state
    )
{
    // keep the timing absolute by adding to the absolute time point
    next = next + std::chrono::nanoseconds(
        (long long) (this->ratio * (double) std::chrono::nanoseconds::period::den)
    );

// ...

这样你只调用 now() 一次,然后你所有的后续时间都是 absolutely (而不是 relatively)从那一点计算。

编辑添加:

此外,我会避免使用 std::chrono::high_resolution_clock。它通常只是 std::chrono::system_clock 的别名,随着系统时钟尝试与 互联网时间 .

保持同步,它会受到随机时间更改的影响

使用std::chrono::steady_clock.