【问题标题】:Why is my rendering thread taking up 100% cpu?为什么我的渲染线程占用 100% cpu?
【发布时间】:2023-03-06 08:28:01
【问题描述】:

所以现在在我的 OpenGL 游戏引擎中,当我的渲染线程实际上无事可做时,它占用了我的 CPU 所能提供的最大值。 Windows 任务管理器显示我的应用程序占用了 25% 的处理(我有 4 个硬件线程,所以 25% 是一个线程可以占用的最大值)。当我根本不启动渲染线程时,我得到 0-2%(这本身就令人担忧,因为它所做的只是运行一个 SDL 输入循环)。

那么,我的渲染线程到底在做什么?这是一些代码:

Timer timer;

while (gVar.running)
{
   timer.frequencyCap(60.0);

   beginFrame();
   drawFrame();
   endFrame();
}

让我们逐一介绍。 Timer 是我使用 SDL_GetPerformanceCounter 制作的自定义计时器类。 timer.frequencyCap(60.0); 旨在确保循环每秒运行的次数不超过 60 次。这是Timer::frequencyCap()的代码:

double Timer::frequencyCap(double maxFrequency)
{
    double duration;

    update();
    duration = _deltaTime;
    if (duration < (1.0 / maxFrequency))
    {
        double dur = ((1.0 / maxFrequency) - duration) * 1000000.0;
        this_thread::sleep_for(chrono::microseconds((int64)dur));
        update();
    }

    return duration;
}

void Timer::update(void)
{
    if (_freq == 0)
        return;

    _prevTicks = _currentTicks;
    _currentTicks = SDL_GetPerformanceCounter();

      // Some sanity checking here. //
      // The only way _currentTicks can be less than _prevTicks is if we've wrapped around to 0. //
      // So, we need some other way of calculating the difference.
    if (_currentTicks < _prevTicks)
   {
         // If we take difference between UINT64_MAX and _prevTicks, then add that to _currentTicks, we get the proper difference between _currentTicks and _prevTicks. //
      uint64 dif = UINT64_MAX - _prevTicks;

         // The +1 here prvents an off-by-1 error.  In truth, the error would be pretty much indistinguishable, but we might as well be correct. //
      _deltaTime = (double)(_currentTicks + dif + 1) / (double)_freq;
   }
   else
      _deltaTime = (double)(_currentTicks - _prevTicks) / (double)_freq;
}

接下来的 3 个函数要简单得多(现阶段):

void Renderer::beginFrame()
{
      // Perform a resize if we need to. //
   if (_needResize)
   {
      gWindow.getDrawableSize(&_width, &_height);
      glViewport(0, 0, _width, _height);
      _needResize = false;
   }

   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}

void Renderer::endFrame()
{
   gWindow.swapBuffers();
}

void Renderer::drawFrame()
{
}

渲染线程是使用 std::thread 创建的。我能想到的唯一解释是 timer.frequencyCap 不知何故不起作用,除了我在我的主线程中使用完全相同的函数并且我空闲在 0-2%。

我在这里做错了什么?

【问题讨论】:

  • 添加日志记录,直到您了解发生了什么。
  • 强烈建议废弃自己的定时器控件,使用显卡驱动提供的swap interval control
  • 任何 sleep_for 调用都保证低于 100% 的 CPU 负载。检查这个条件if (duration &lt; (1.0 / maxFrequency))
  • 我打开了交换间隔,它提高了 cpu 使用率(我下降到了 10-15%),但是删除了我的计时器使它又回到了 25%。如果我设置frequencyCap(30) 而不是60,我的cpu 使用率下降到0。
  • sleep_for 可能正在使用繁忙的循环来创建延迟,本质上是start=now();while(start+delay&lt;now());

标签: c++ multithreading performance opengl sdl


【解决方案1】:

如果启用了垂直同步并且您的程序遵守交换间隔,那么您看到程序占用 100% 实际上是 Windows 测量 CPU 时间的一种人工产物。这是一个早就知道的问题,但是任何时候您的程序在驱动程序上下文中阻塞(这是当 OpenGL 在 V-Sync 上阻塞时发生的情况)窗口都会将此解释为程序实际消耗 CPU 时间,而实际上它只是处于空闲状态。

如果您在交换缓冲区之后添加Sleep(1),它将欺骗 Windows 进行更理智的记帐;在某些系统上,即使是 Sleep(0) 也能解决问题。

无论如何,大部分时间 100% 只是表面问题。


在过去的几周里,我对低延迟渲染(即最小化用户输入和相应光子从显示器出来之间的时间)进行了一些详尽的研究,因为我很快就会得到一个 VR 耳机。以下是我发现的有关时序 SwapBuffers 的信息:该问题的合理解决方案实际上是对帧渲染时间进行计时,并在 SwapBuffers 之前添加人工睡眠,以便您在 V-Sync 之前仅几毫秒醒来。然而,这说起来容易做起来难,因为 OpenGL 是高度异步的,显式添加同步会降低吞吐量。

【讨论】:

  • +1 这也可以解释我过去反击的一些 GL 怪异
  • @Spektre:等到您开始对各种 OpenGL 相关调用进行分析时间线。 SwapBuffers 和改变 OpenGL 调用的第一个帧缓冲区内容之间发生的事情真的很奇怪……而且不一致。我发现在 static 场景中启用 V-Sync 后,流水线停顿会出现几毫秒的偏差。对于这样的测试,您会期望一些非常一致的值。显然,在 OS 进程调度程序、GPU 和 OpenGL 驱动程序之间发生了一些非常奇怪的事情。当我更改调度程序参数时,我还没有试验过会发生什么。
  • XP+ 的 Windows 任务调度程序很奇怪......我在速度较慢的机器(甚至 2 个核心)和少数线程(约 3 个不要求)(与 OpenGL 无关)上遇到问题,其中一个正在访问异步 USB传输,它会定期挂断几毫秒到几秒钟。这让我在 W7 上疯了,在 W2K 上更糟。它只发生在低端硬件(如一些 CELERON 笔记本电脑......)上,即使 CPU 足以处理它......我怀疑它与访问驱动程序层有关(这也适用于 GL 的东西)。这冻结了一切甚至不相关的东西......
  • 有趣的是,在 w2k 上,即使在速度较慢/较旧的设置上也一切顺利 :) 我所做的只是一些 FIFO 同步全双工 USB 批量传输,总流量为 4.4MB/s ...
  • @Spektre:John Carmacs 对 Windows 调度程序的看法:twitter.com/ID_AA_Carmack/status/507264040079728641
【解决方案2】:

如果您有复杂的场景或未优化的渲染

  • 在某处遇到瓶颈或 gl 代码有错误
  • 然后,无论场景多么复杂,帧速率通常都会下降到 20 fps 左右(至少在 NVidia 上)
  • 对于非常复杂的场景,甚至低于它

试试这个:

  1. 尝试测量处理此问题所需的时间

    beginFrame();
    drawFrame();
    endFrame();
    
    • 在那里你会看到你的 fps 限制
    • 将其与场景复杂性/硬件功能进行比较
    • 并确定是错误还是过于复杂的场景
    • 尝试关闭一些 GL 功能
    • 例如上周我发现,如果我关闭 CULL_FACE,它实际上会加速我的一个非优化渲染大约 10-100 倍,直到今天我才明白为什么(关于旧的 GL 代码)
  2. 检查 GL 错误

  3. 我在您的代码中没有看到任何 glFlush()/glFinish()

    • 尝试使用 glFinish() 进行测量;
  4. 如果你不能解决这个问题,你仍然可以使用像这样的肮脏技巧

    • Sleep(1); 添加到您的代码中
    • 它会强制让你的线程休眠,因此它永远不会使用 100% 的电源
    • 它的休眠时间是 1 毫秒 + 调度程序粒度,因此它也限制了目标 fps
    • 你使用this_thread::sleep_for(chrono::microseconds((int64)dur));
    • 不知道那个函数你真的确定它和你想的一样吗?

【讨论】:

  • 现在,drawFrame() 几乎什么都没做。场景既不复杂也不简单,因为场景并不存在。我打电话给glClearSDL_GL_SwapWindow。这就是为什么我如此困惑。将glFinish() 添加到代码中实际上以某种方式使其更糟 - 我的使用率从 25% 上升到了 45-50%! glFlush没有这个问题,也没有帮助。
  • @HaydnV.Harach 不测量 CPU 使用率,而是测量代码所花费的时间。只有这样你才能找到问题所在。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-09-16
  • 1970-01-01
  • 1970-01-01
  • 2012-03-10
  • 1970-01-01
  • 2012-12-29
  • 1970-01-01
相关资源
最近更新 更多