4

仅游戏循环就使用了 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

            }
        }
4

12 回答 12

8

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

于 2010-03-02T13:08:51.447 回答
5

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

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

于 2010-03-02T13:09:04.183 回答
3

That's a standard game loop for action games, where you must update objects positions / game world.
If you are making a board game GetMessage would be a better choice.
It really depends on what game you are making.

于 2010-03-02T13:40:10.373 回答
2

当您没有消息要处理并且没有游戏代码要执行时,您需要放弃 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();
               }
            }
         }
      }
于 2010-03-04T00:26:43.020 回答
1

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

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

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

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

于 2010-03-02T13:33:07.333 回答
1

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

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

于 2011-11-26T09:23:06.803 回答
1

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

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

于 2011-01-14T15:49:04.510 回答
1

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

于 2010-03-02T13:13:44.313 回答
0

它看起来不像标准的 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

于 2010-03-02T13:22:03.163 回答
0

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

我不知道你是想要更多的电力(从而达到 100%)还是更少的电力(并且少用可怜的用户电费......)

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

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

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

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

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

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

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

于 2010-05-09T22:30:02.520 回答
0

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

sleep(0);

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

于 2010-03-02T13:13:51.793 回答
0

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

我在这里遇到了同样的问题(应用程序空闲时 CPU 占 50%),使用 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;
于 2016-08-09T20:30:33.863 回答