【问题标题】:Windows Game Loop 50% CPU on Dual Core双核 Windows 游戏循环 50% CPU
【发布时间】:2011-01-22 17:19:18
【问题描述】:

仅游戏循环就占用了 50% 的 CPU 使用率,我还没有做任何渲染工作。我在这里做什么?

        while(true)
        {
            if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
            {
                    if(msg.message == WM_QUIT || 
                           msg.message == WM_CLOSE || 
                           msg.message == WM_DESTROY)
                            break;

                    TranslateMessage(&msg);
                    DispatchMessage(&msg);                   
            }
            else
            {
                    //Run game code, break out of loop when the game is over

            }
        }

【问题讨论】:

  • 这就是你的全部代码?您的分析器显示大部分时间占用了什么?

标签: winapi message-queue game-loop


【解决方案1】:

这个问题很老了,但我相信我的推荐可能会对新读者有所帮助。

我在这里遇到了同样的问题(50% 的 CPU 处于应用空闲状态),使用 Delphi(没有 VCL)运行 Win7 32 位 Core Duo 的纯 Win32 应用程序。

我在消息循环中尝试了 Sleep(0)、Sleep(1) 等,但都没有将 CPU 使用率降低到 0%(在应用空闲时)。最终,我成功地使用了相同的 Sleep(),不再在每个循环周期中,但前提是 PeekMessage() 返回 False。 现在,当应用程序空闲时,我得到 0% 的 CPU 使用率。

在简化的代码中,这就是我现在正在做的事情:

AppIsDone := False;  // turned on after wm_Close (not shown below)

repeat
  if not PeekMessage(...) then
  begin
    Sleep(1);
    Continue;  {repeat}
  end;

  if GetMessage(...) then
  begin
    TranslateMessage(...);
    DispatchMessage(...);
  end;
until AppIsDone;

【讨论】:

    【解决方案2】:

    这是因为 PeekMessage 函数不会删除 WM_PAINT 消息,所以它总是返回 TRUE。 MSDN 说:

    PeekMessage 函数通常不会从队列中删除 WM_PAINT 消息。 WM_PAINT 消息将保留在队列中,直到它们被处理。但是,如果 WM_PAINT 消息的更新区域为 NULL,则 PeekMessage 会将其从队列中删除。

    【讨论】:

      【解决方案3】:

      我遇到了同样的问题,在这里得到了答案: Game Loops

      在我的程序中,我使用了上面文章“恒定游戏速度和最大 FPS”中的最后一个循环

      【讨论】:

        【解决方案4】:

        经典的忙/等待循环。您的 CPU 正忙于检查(并无限期地重新检查)消息。您需要以阻塞的方式等待消息,或者更有可能使用一个计时器来定期唤醒您的游戏线程,以便它可以完成工作。然后游戏线程会消失,直到下一次被唤醒。

        【讨论】:

        • 对于具有正常渲染循环的高性能游戏来说,计时器不是正确的方法,它需要尽可能快地渲染并占用 CPU 周期。
        • John 是对的,但是您仍然应该使用计时器来获取自上一帧以来的滴答声,以便您可以相应地更新游戏状态。例如,无论帧速率如何,门都应该以特定速率打开。您可以使用计时器来计算自上一帧以来的正确移动量。在 Windows 上,使用 QueryPerformanceCounter。
        • 不,它不需要尽可能快地渲染。渲染速度快于屏幕刷新率是没有意义的。最好 - 总是 - 能够阻止至少下一个屏幕间隔 - 大多数图形框架(opengl / directx)仅在运行独占/全屏应用程序时允许显示间隔同步。下一个选项是选择一个“足够好”的计时器——通常可以实现>30fps的帧速率,但不会真正快于100fps。所以通常使用 10 到 33ms 的定时器。
        • 抱歉,我曾在游戏行业工作过,但事实并非如此。大多数游戏都允许您选择是否启用垂直同步 - 除了其他任何方法之外,这是他们可以使用游戏在新硬件上进行性能测试的唯一方法,例如“Doom3 在新的 nVidia 999X 上以令人印象深刻的 400fps 运行”
        • 当你的显示器刷新率为 60Hz 时,除了证明你的 ePeen 的大小之外,400fps 还能做什么?
        【解决方案5】:

        您的游戏在一个内核中运行得尽可能快。这很正常,具体取决于您的操作。

        我不知道你是想用更多的电(从而达到 100%)还是用更少的电(减少糟糕的用户电费...)

        如果你想获得更多的权力,你需要以某种方式使用线程,以使用两个内核。由于我不太喜欢线程,所以我什至不会尝试解释,这完全不是我的领域。

        如果你想使用 LESS 电源,还有两种选择...

        正如某些游戏库 API(如 Allegro)所称,其中之一是“屈服”,它包括制作一个 FPS 计数器,并在足够的时间内将控制权交还给每帧的 CPU。例如,如果您的游戏想要以 120 FPS 运行,并且您希望它以 60 FPS 运行,您可以给他大约与计算一帧所花费的时间相同的时间(大约 8,3333...ms)。

        另一种是使用基于事件的方式来编写游戏代码,而不是循环。在这种形式中,您将代码放在一个名为“Update”的函数中,该函数接受自上次调用以来所花费的时间量作为参数(这非常重要......)。这个“更新”可以是整个应用程序(更经典),也可以是每个对象都有自己的(在 Flash 发明后流行,使用它,如“OnEnterFrame”,Unity 也使用它,以及其他一些引擎)。然后您编写一个引发中断并调用该更新的代码,通常只是一个定期运行的计时器(60FPS 为 16.6.... ms,30FPS 为 33.33333... ms)。

        显然,还有很多其他方法,但我不会全部解释,因为这对于一本小书来说已经足够了......

        我自己使用了各种方法,我喜欢只使用 CPU 完全爆炸(不使用线程),并在电源可用时使用越来越多的系统滥用效果。但是对于更简单的游戏,我通常会创建一个“产生”的循环,最简单的方法通常是计算计算所有内容所花费的时间,从 16.6 毫秒中减去,然后调用一个睡眠函数(在那个时间段内将控制权交给操作系统)结果......就像,如果计算一个帧需要 3 毫秒,我称之为睡眠(16-3)。几个引擎迫使我使用“事件风格”(即:输入来自键盘、鼠标和操纵杆中断,以及计时器中断并调用“Update(step)”),我不喜欢它,但我必须学习。 ..

        最后一点:我称之为“step”变量(更新参数),通常用于游戏逻辑数学中,例如:“position.x = position.x + speed*step”来制作一个对象以实际恒定速度移动...(显然,所使用的操作取决于“步”所代表的内容)。

        【讨论】:

          【解决方案6】:

          当您没有要处理的消息并且没有要执行的游戏代码时,您需要放弃 CPU。 一种方法是使用MsgWaitForMultipleObjects 等待消息出现在您的队列中一段时间到期。

          类似的东西

             DWORD g_msNextGameCall;
             DWORD g_msGameTickTime = 1000/75;
          
             while (true)
                {
                if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD))
                   {
                   if (WM_QUIT == msg.message)
                      break;
          
                   TranslateMessage(&msg);
                   DispatchMessage(&msg);  
                   }
                else
                   {
                   DWORD ms = GetTickCount();
                   DWORD msNext = g_msNextGameCall;
                   LONG  lWait = 0;
                   DWORD dwRet = WAIT_TIMEOUT;
          
                   if (ms < msNext)
                      lWait = min((LONG)g_msGameTickTime, (LONG)(msNext - ms));
          
                   if (lWait <= 1)
                      {
                      g_msNextGameCall = ms + g_msGameTickTime;
                      DoGameStuff();
                      }
                   else
                      {
                      if (WAIT_TIMEOUT == MsgWaitForMultipleObjects (0, NULL, FALSE, lWait, QS_ALLEVENTS))
                         {
                         g_msNextGameCall = GetTickCount() + g_msGameTickTime;
                         DoGameStuff();
                         }
                      }
                   }
                }
          

          【讨论】:

          【解决方案7】:

          这是动作游戏的标准游戏循环,您必须在其中更新对象位置/游戏世界。
          如果您正在制作棋盘游戏GetMessage 会是更好的选择。
          这真的取决于你在制作什么游戏。

          【讨论】:

          • 好点。如果您在渲染循环中需要高帧率,这取决于 lot。如果不这样做,请将其编码为更像普通的 Windows 应用程序。
          【解决方案8】:

          我认为这种行为是意料之中的。每当您的游戏代码什么都不做时,应用程序就会使用 PeekMessage 疯狂地检查消息队列 - 这是一个连续循环,因此使用了整个 1 个内核。

          当您向 else{...} 块添加逻辑时,您会发现它在一个内核上保持 100% 的使用率,但时间花在了您的游戏逻辑上 - 只有未使用的 CPU 周期用于 PeekMessage 调用,但是现在 100% 的周期都没有使用。

          如果游戏在全屏运行时,通常会在可见时最大化 CPU。但是您可能应该考虑使用GetMessage 而不是PeekMessage

          请注意,游戏的运行方式通常与普通应用不同。普通应用程序通常什么都不做,除非他们收到一条消息告诉他们做某事。游戏通常一直在做一些事情,因为它们希望渲染尽可能多的帧/秒。不过,在窗口模式下窃取 所有 CPU 有点贪心。

          【讨论】:

          • 好吧,如果其他东西实际上正在运行操作系统仍然有发言权,它不应该窃取所有这些
          • 游戏在全屏运行时通常不想多任务。他们想要他们可以抓住的每一点性能。
          • 如果你不需要它,不要使用所有 CPU 仍然是礼貌的。但是,这不会自动成为问题。您正在使用所有 CPU,但这仅仅是因为没有其他应用程序试图使用它 - 您仍在检查消息循环,因此其他应用程序仍然可以缓慢运行。
          • 是的,我想我不想在应用程序最小化或处于非活动状态时使用游戏循环,但游戏循环仍会使用所有可用资源和激活时尽可能多的 CPU。
          【解决方案9】:

          它看起来不像标准的 win32 应用程序主循环... 类似于

          int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
           {
             MSG msg;
             while(GetMessage(&msg, NULL, 0, 0) > 0)
             {
               TranslateMessage(&msg);
               DispatchMessage(&msg);
             }
             return msg.wParam;
           }
          

          当您在 while (true) 循环中时,甚至用户事件(鼠标和键盘)都无法在消息队列中正确分派

          如果您的目标是在 win32 中开发游戏应用程序,我建议您查看 Directx

          【讨论】:

          • 你会在哪里运行游戏代码来使用 GetMessage() 更新游戏对象?
          • -1 我不知道从哪里开始...it doesnt look as a standard win32 app main loop - 它不应该...这是一个游戏。 If your goal is to develop a Game app in win32 i suggest you to look at Directx - 这甚至都不是问题... sigh
          【解决方案10】:

          在你的 else 块中,尝试添加:

          sleep(0);
          

          这将导致您的线程让出 CPU,从而打破忙等待循环。要编写实际的游戏代码,请按照 tvanfosson 的建议使用计时器来唤醒另一个游戏线程。

          【讨论】:

            【解决方案11】:

            如果您正在制作一个非常简单的游戏并在主循环中进行所有渲染和计算,您需要控制 while 循环的运行速度,否则游戏在不同处理器上的运行速度会大相径庭。这样做的一个副作用是,您还要确保 while 循环不会消耗任何 CPU。

            【讨论】:

              【解决方案12】:

              您已经创建了一个忙等待循环。您可能正在使用 100% 的单核,因此使用 50% 的双核。

              您需要找到一种方法来阻止读取(在单独的线程中),根据需要阻止和退出 I/O 调用,或者在线程中执行其他有用的操作。每种策略都有其优点和缺点。单独的线程需要同步通信方法,例如互斥锁。退出 I/O 意味着当没有消息时,此线程中不会发生任何其他有用的事情。在循环中执行其他操作可能会导致处理混乱(“其他操作”在更少的消息上得到更多的处理。在更多的消息上得到更少的处理)。

              【讨论】:

              • 您不需要第二个线程。这是非常基本的游戏编程,调整正常的消息循环是完全标准的。
              猜你喜欢
              • 1970-01-01
              • 2014-01-21
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-09-15
              相关资源
              最近更新 更多