C++ 主循环偶尔会卡顿
C++ main loop stutters occasionally
我遇到了一个问题,我的游戏循环卡顿大约每秒一次(可变间隔)。单帧需要 60 毫秒以上,而所有其他帧需要不到 1 毫秒。
经过大量简化后,我以重现错误的以下程序结束。它只测量帧时间并报告它。
#include <iostream>
#include "windows.h"
int main()
{
unsigned long long frequency, tic, toc;
QueryPerformanceFrequency((LARGE_INTEGER*)&frequency);
QueryPerformanceCounter((LARGE_INTEGER*)&tic);
double deltaTime = 0.0;
while( true )
{
//if(deltaTime > 0.01)
std::cerr << deltaTime << std::endl;
QueryPerformanceCounter((LARGE_INTEGER*)&toc);
deltaTime = (toc - tic) / double(frequency);
tic = toc;
if(deltaTime < 0.01) deltaTime = 0.01;
}
}
同样,许多帧中的一帧比其他帧慢得多。添加 if
让错误消失(那时永远不会调用 cerr)。我的原始问题不包含任何 cerr/cout。但是,我认为这是同一错误的重现。
cerr 在每次迭代中都会被刷新,因此创建单个慢帧时不会发生这种情况。我从探查器(非常困)知道流在内部使用 lock/critical 部分,但这不应该改变任何东西,因为程序是单线程的。
是什么导致单次迭代停滞那么多?
编辑:我做了更多测试:
- 添加
std::this_thread::sleep_for( std::chrono::milliseconds(7) );
并因此减少进程 CPU 利用率不会改变任何东西。
- 有了
printf("%f\n", deltaTime);
问题就消失了(可能是因为它没有使用与流相反的互斥锁和内存分配)
我不确定,但可能是系统正在中断您的主线程让其他人 运行,因为这需要一些时间(我记得在我的 Windows XP pc 量程为 10ms),它会停止一帧。
这是非常明显的,因为它是一个单线程应用程序,如果您使用多个线程,它们通常会在处理器的多个内核上分派(如果可用),并且停顿仍然会存在但不太重要(如果您正确地实现了您的应用程序逻辑)。
编辑:here 您可以获得有关 windows 和 linux 调度程序的更多信息。基本上,windows 使用量程(在 Windows 服务器上从几毫秒到 120 毫秒不等)。
编辑2:可以看到更详细的explanation on the windows scheduler here。
windows的设计不保证任何执行时间的上限,因为它使用某种逻辑动态分配运行时间资源给所有程序 - 例如,调度程序将分配资源到具有高优先级的进程,并在某些情况下饿死低优先级进程。如果程序 运行 紧密循环并消耗大量 CPU 资源,那么程序在统计上更有可能 - 最终 - 受到此类事情的影响。因为 - 最终 - 调度程序将暂时提高正在挨饿的程序的优先级 and/or 降低其他正在挨饿的程序的优先级(在你的情况下,通过 运行 紧循环)。
将输出设置为 std::cerr
条件并不会改变这种情况发生的事实 - 它只是改变了它在指定时间间隔内发生的可能性,因为它改变了程序使用系统资源的方式循环,因此改变了它与系统调度程序、策略等交互的方式。
这种事情会影响所有非实时操作系统中的程序 运行ning,尽管确切的影响取决于每个 OS 的实现方式(例如调度策略、其他控制访问的策略程序到资源等)。这种停顿发生的概率总是非零的(即使很小)。
如果你想绝对保证在这些事情上没有停顿,你将需要一个实时操作系统。这些系统被设计成在时间意义上更可预测地做事,但这需要权衡取舍,因为它还要求您的程序在设计时就知道它们必须在指定的时间间隔内完成指定功能的执行。实时操作系统使用不同的策略,但如果程序在设计时没有考虑到这些问题,它们强制执行约束时间可能会导致程序出现故障。
我遇到了一个问题,我的游戏循环卡顿大约每秒一次(可变间隔)。单帧需要 60 毫秒以上,而所有其他帧需要不到 1 毫秒。
经过大量简化后,我以重现错误的以下程序结束。它只测量帧时间并报告它。
#include <iostream>
#include "windows.h"
int main()
{
unsigned long long frequency, tic, toc;
QueryPerformanceFrequency((LARGE_INTEGER*)&frequency);
QueryPerformanceCounter((LARGE_INTEGER*)&tic);
double deltaTime = 0.0;
while( true )
{
//if(deltaTime > 0.01)
std::cerr << deltaTime << std::endl;
QueryPerformanceCounter((LARGE_INTEGER*)&toc);
deltaTime = (toc - tic) / double(frequency);
tic = toc;
if(deltaTime < 0.01) deltaTime = 0.01;
}
}
同样,许多帧中的一帧比其他帧慢得多。添加 if
让错误消失(那时永远不会调用 cerr)。我的原始问题不包含任何 cerr/cout。但是,我认为这是同一错误的重现。
cerr 在每次迭代中都会被刷新,因此创建单个慢帧时不会发生这种情况。我从探查器(非常困)知道流在内部使用 lock/critical 部分,但这不应该改变任何东西,因为程序是单线程的。
是什么导致单次迭代停滞那么多?
编辑:我做了更多测试:
- 添加
std::this_thread::sleep_for( std::chrono::milliseconds(7) );
并因此减少进程 CPU 利用率不会改变任何东西。 - 有了
printf("%f\n", deltaTime);
问题就消失了(可能是因为它没有使用与流相反的互斥锁和内存分配)
我不确定,但可能是系统正在中断您的主线程让其他人 运行,因为这需要一些时间(我记得在我的 Windows XP pc 量程为 10ms),它会停止一帧。
这是非常明显的,因为它是一个单线程应用程序,如果您使用多个线程,它们通常会在处理器的多个内核上分派(如果可用),并且停顿仍然会存在但不太重要(如果您正确地实现了您的应用程序逻辑)。
编辑:here 您可以获得有关 windows 和 linux 调度程序的更多信息。基本上,windows 使用量程(在 Windows 服务器上从几毫秒到 120 毫秒不等)。
编辑2:可以看到更详细的explanation on the windows scheduler here。
windows的设计不保证任何执行时间的上限,因为它使用某种逻辑动态分配运行时间资源给所有程序 - 例如,调度程序将分配资源到具有高优先级的进程,并在某些情况下饿死低优先级进程。如果程序 运行 紧密循环并消耗大量 CPU 资源,那么程序在统计上更有可能 - 最终 - 受到此类事情的影响。因为 - 最终 - 调度程序将暂时提高正在挨饿的程序的优先级 and/or 降低其他正在挨饿的程序的优先级(在你的情况下,通过 运行 紧循环)。
将输出设置为 std::cerr
条件并不会改变这种情况发生的事实 - 它只是改变了它在指定时间间隔内发生的可能性,因为它改变了程序使用系统资源的方式循环,因此改变了它与系统调度程序、策略等交互的方式。
这种事情会影响所有非实时操作系统中的程序 运行ning,尽管确切的影响取决于每个 OS 的实现方式(例如调度策略、其他控制访问的策略程序到资源等)。这种停顿发生的概率总是非零的(即使很小)。
如果你想绝对保证在这些事情上没有停顿,你将需要一个实时操作系统。这些系统被设计成在时间意义上更可预测地做事,但这需要权衡取舍,因为它还要求您的程序在设计时就知道它们必须在指定的时间间隔内完成指定功能的执行。实时操作系统使用不同的策略,但如果程序在设计时没有考虑到这些问题,它们强制执行约束时间可能会导致程序出现故障。