Игровой цикл Windows 50% ЦП на двухъядерном процессоре

Только игровой цикл использует 50% загрузки ЦП, я еще не занимался рендерингом. Что я здесь делаю?

        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

            }
        }

person cpx    schedule 02.03.2010    source источник
comment
Это весь ваш код? Что, по вашему профайлеру, занимает большую часть времени?   -  person glenatron    schedule 02.03.2010


Ответы (12)


Классический цикл занятости/ожидания. Ваш процессор усердно проверяет (и перепроверяет до бесконечности) сообщения. Вам нужно ждать сообщений блокирующим образом или, что более вероятно, использовать таймер, который периодически пробуждает ваш игровой поток, чтобы он мог выполнять свою работу. После этого игровой поток исчезнет до следующего пробуждения.

person tvanfosson    schedule 02.03.2010
comment
Таймер — не лучший подход для высокопроизводительной игры с обычным циклом рендеринга, он должен рендериться как можно быстрее и быть жадным до циклов ЦП. - person Mr. Boy; 02.03.2010
comment
Джон прав, НО вы все равно должны использовать таймер, чтобы получить тики с последнего кадра, чтобы вы могли соответствующим образом обновить свое игровое состояние. Например, дверь должна открываться с определенной скоростью независимо от частоты кадров. Вы можете использовать таймер для расчета правильного количества движения с момента последнего кадра. В Windows используйте QueryPerformanceCounter. - person Matthew Olenik; 02.03.2010
comment
Да, это правда, логика должна использовать реальное течение времени, но рендеринг должен выполняться как можно быстрее. как вы делаете свою игру независимой от частоты кадров — это другой вопрос. - person Mr. Boy; 02.03.2010
comment
Нет, это не нужно рендерить как можно быстрее. рендеринг быстрее, чем частота обновления экрана, бессмысленно. Желательно - всегда - иметь возможность блокировки хотя бы на следующий интервал экрана - большинство графических фреймворков (opengl/directx) разрешают синхронизацию интервала отображения только при запуске эксклюзивных/полноэкранных приложений. Следующий вариант — выбрать достаточно хороший таймер — обычно для достижения частоты кадров > 30 кадров в секунду, но не выше 100 кадров в секунду. Поэтому обычно используется таймер от 10 до 33 мс. - person Chris Becke; 02.03.2010
comment
Извините, я работал в игровой индустрии, и это неправда. Большинство игр позволяют вам выбирать, включать ли vsync или нет — кроме всего прочего, это единственный способ использовать игры для тестирования производительности на новом оборудовании, например, Doom3 работал со впечатляющей скоростью 400 кадров в секунду на новой nVidia 999X. - person Mr. Boy; 02.03.2010
comment
а 400 кадров в секунду не доказывают размер вашего ePeen, когда частота обновления вашего монитора составляет 60 Гц? - person Chris Becke; 02.03.2010
comment
Более высокая частота кадров означает немного меньшую задержку, что очень важно для видеоигр. Небольшой да, но многие небольшие суммы ниже складываются. И вы можете продолжить это, утверждая, что эта задержка не имеет значения, но в видеоиграх это общепринятая проблема, и не слишком много игроков будут интересоваться вашими аргументами. Высокая частота кадров не является проблемой ePenis для большинства людей. Это просто глупо. - person John Chadwick; 14.07.2014

Вы создали цикл занятого ожидания. Вы, вероятно, используете 100% одного ядра, то есть 50% двухъядерного.

Вам нужно найти способ блокировать чтение (в отдельном потоке), блокировать и отключать вызов ввода-вывода по мере необходимости или делать что-то еще полезное в потоке. Каждая стратегия имеет свои преимущества и недостатки. Отдельным потокам нужны синхронизированные методы связи, такие как мьютексы. Выпадение ввода-вывода означает, что в этом потоке не происходит ничего полезного, когда нет сообщений. Выполнение чего-то еще в цикле может привести к неравномерной обработке («что-то еще» обрабатывается больше при меньшем количестве сообщений, меньше при большем количестве сообщений).

person kmarsh    schedule 02.03.2010
comment
Вам не нужен второй поток. Это очень простое игровое программирование, настройка обычного цикла сообщений совершенно стандартна. - person Mr. Boy; 02.03.2010

Это стандартный игровой цикл для боевых игр, где вы должны обновлять позиции объектов/игровой мир.
Если вы делаете настольную игру, GetMessage будет лучшим выбором.
Это действительно зависит от того, какую игру вы делаете.

person Nick Dandoulakis    schedule 02.03.2010
comment
Хорошая точка зрения. Это много зависит от того, нужна ли вам высокая частота кадров в цикле рендеринга или нет. Если вы этого не сделаете, напишите код как обычное приложение для Windows. - person Mr. Boy; 02.03.2010

Вам нужно отказаться от ЦП, когда у вас нет сообщений для обработки и кода игры для выполнения. Один из способов сделать это — использовать 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();
               }
            }
         }
      }
person John Knoeller    schedule 04.03.2010
comment
???? Похожая идея упоминалась здесь: devblogs.microsoft.com/oldnewthing/20060126 -00/?p=32513 - person Amro; 30.09.2019

Если вы делаете очень простую игру и выполняете весь рендеринг и расчеты в основном цикле, вам необходимо контролировать скорость выполнения цикла while, иначе игра будет работать с совершенно разными скоростями на разных процессорах. В качестве побочного эффекта вы также убедитесь, что цикл while не потребляет никакого процессора, ничего не делая.

person Cbsch    schedule 02.03.2010

Я думаю, такое поведение ожидаемо. Всякий раз, когда ваш игровой код ничего не делает, приложение яростно проверяет очередь сообщений, используя PeekMessage — это непрерывный цикл, поэтому использует все 1 ядро.

Когда вы добавите логику в свой блок else{...}, вы обнаружите, что он по-прежнему использует 100 % на одном ядре, но время тратится на выполнение вашей игровой логики — только неиспользуемые циклы ЦП используются для вызова PeekMessage, но прямо сейчас 100 % циклы не используются.

Обычно игра максимально загружает ЦП, когда она видна, если она работает в полноэкранном режиме. Но вам, вероятно, следует рассмотреть возможность использования GetMessage вместо PeekMessage.

Обратите внимание, что игры обычно не работают так же, как обычные приложения. Обычные приложения обычно ничего не делают, если только они не получают сообщение, говорящее им что-то сделать. Игры обычно делают что-то постоянно, потому что они хотят отображать как можно больше кадров в секунду. Тем не менее, кража всего процессора немного прожорлив в оконном режиме.

person Mr. Boy    schedule 02.03.2010
comment
ну, он не должен красть все это, если на самом деле работает что-то еще, ОС все еще имеет право голоса - person jk.; 02.03.2010
comment
игры обычно не хотят многозадачности при работе в полноэкранном режиме. Им нужен каждый бит производительности, который они могут получить. - person Mr. Boy; 02.03.2010
comment
по-прежнему вежливо не использовать весь ЦП, если он вам не нужен. Но это не проблема автоматически. Вы используете весь ЦП, но только потому, что никакие другие приложения не пытаются его использовать — вы все еще проверяете цикл сообщений, поэтому другие приложения могут работать медленно. - person Mr. Boy; 02.03.2010
comment
Да, я думаю, я не хочу использовать игровой цикл, когда приложение свернуто или неактивно, но игровой цикл по-прежнему будет использовать все доступные ресурсы и столько ЦП, сколько он может, когда он активирован. - person cpx; 03.03.2010

У меня была та же проблема, и я получил ответ здесь: Игровые циклы

В своей программе я использовал последний цикл из статьи выше "Постоянная скорость игры с максимальным FPS"

person Yevhen    schedule 14.01.2011

Это связано с тем, что функция PeekMessage не удаляет сообщение WM_PAINT, поэтому она всегда возвращает TRUE. MSDN говорит:

Функция PeekMessage обычно не удаляет сообщения WM_PAINT из очереди. Сообщения WM_PAINT остаются в очереди до тех пор, пока не будут обработаны. Однако, если сообщение WM_PAINT имеет область обновления NULL, PeekMessage удаляет его из очереди.

person Hoa    schedule 26.11.2011

В блоке else попробуйте добавить это:

sleep(0);

Это приведет к тому, что ваш поток уступит ЦП, разорвав цикл ожидания. Чтобы выполнить ваш реальный игровой код, используйте таймер, как предлагает tvanfosson, чтобы разбудить другой игровой поток.

person Eric Petroelje    schedule 02.03.2010

это не выглядит как стандартный основной цикл приложения 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.

person Luca Rocchi    schedule 02.03.2010
comment
Где бы вы запускали код игры для обновления игровых объектов с помощью GetMessage()? - person cpx; 02.03.2010
comment
-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 - вопрос даже не в этом... вздох - person Nathan Osman; 10.05.2010

Ваша игра работает максимально быстро на одном ядре. Это нормально, в зависимости от того, что вы сделали.

Я не знаю, хотите ли вы получить БОЛЬШУЮ мощность (таким образом, достигнув 100%) или МЕНЬШЕ мощности (и использовать меньше энергии бедного пользователя...)

Если вы хотите получить БОЛЬШУЮ мощность, вам нужно каким-то образом использовать многопоточность, чтобы задействовать оба ядра. Поскольку я не сильно увлекаюсь потоковой передачей, я даже не буду пытаться объяснять, это совершенно не моя область.

Если вы хотите использовать МЕНЬШЕ энергии, есть также два варианта...

Одним из них является «выход», как его называют некоторые API-интерфейсы игровых библиотек (например, Allegro), он состоит из создания счетчика FPS и передачи управления процессору в каждом кадре в течение достаточного времени. Например, если ваша игра хочет работать со скоростью 120 кадров в секунду, а вы хотите, чтобы она работала со скоростью 60, вы можете дать ему примерно такое же количество времени, которое требуется для расчета кадра (около 8,3333... мс).

Другой - использовать основанный на событиях способ кодирования игры вместо зацикливания. В этой форме вы помещаете свой код в функцию с именем «Обновить», которая принимает в качестве аргумента количество времени, прошедшее с момента последнего вызова (очень важно это...). Это «обновление» может быть либо для всего приложения (более классическое), либо для каждого объекта, имеющего свое (популярное после изобретения Flash, которое использует это, как «OnEnterFrame», также Unity использует это и некоторые другие движки). И вы делаете код, который вызывает прерывание и вызывает это обновление, обычно просто таймер, который запускается в обычное время (16,6.... мс для 60 кадров в секунду, 33,33333... мс для 30 кадров в секунду).

Очевидно, что есть МНОГО и других способов, но я не буду объяснять их все, потому что этого достаточно для небольшой книги...

Я сам использовал различные подходы, мне нравится просто использовать полную мощность процессора (без многопоточности) и использовать все больше и больше системных оскорбительных эффектов по мере того, как мощность становится доступной. Но для более простых игр я обычно делаю цикл, который «уступает», проще всего обычно подсчитывая, сколько времени ушло на все вычисления, вычитаю это из 16,6 мс и вызываю функцию сна (которая дает управление ОС на это время) на результат... Например, если для расчета кадра потребовалось 3 мс, я вызываю сон (16-3). И несколько движков заставляют меня работать с "событийным стилем" (т.е. ввод осуществляется с прерываний клавиатуры, мыши и джойстика, а таймер прерывает и вызывает "Update(step)"), мне это не нравится, но мне пришлось научиться. ..

И последнее замечание: переменная «шаг», как я назвал (аргумент обновления), обычно используется в математической логике игры, например: «position.x = position.x + speed*step», чтобы заставить объект двигаться с фактической скоростью. постоянная скорость... (очевидно, используемая операция зависит от того, какой "шаг" представляет).

person speeder    schedule 09.05.2010

Вопрос старый, но я считаю, что мой отзыв может помочь новым читателям.

Я столкнулся с той же проблемой здесь (50% ЦП в режиме ожидания приложения) с чистым приложением Win32, использующим Delphi (без VCL), работающим под управлением 32-разрядной версии Win7, Core Duo.

Я пробовал Sleep (0), Sleep (1) и т. Д. В цикле сообщений, но ни один из них не снизил загрузку ЦП до 0% (при простое приложения). В конце концов мне удалось использовать тот же Sleep(), но не в каждом цикле цикла, а только в том случае, если PeekMessage() возвращает False. Теперь я получаю 0% использования ЦП, когда приложение простаивает.

В упрощенном коде это то, что я делаю сейчас:

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;
person Paulo França Lacerda    schedule 09.08.2016