Профилирование времени цикла джиттера в циклах - PullRequest
3 голосов
/ 18 января 2012

У меня небольшая проблема при создании игры, и, хотя проблема не ограничивается программированием игры как таковой, я приведу ее на конкретном примере, который я испытываю:

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

Я хотел бы устранить оба.Я хотел бы устранить нормальный джиттер, чтобы сделать рисунок более плавным, и я хочу устранить шипы, потому что они заметны и раздражают.Проблема в том, что я не знаю, какой код вызывает их появление.Я попытался измерить пару конкретных путей кода или собрать данные об использовании системных ресурсов, но пока это только помогает.

То, что я бы хотел бы сделать , - это возможность запустить процессорпрофилировщик, сбрасывая свои данные счетчика каждый цикл.Таким образом, я мог бы легко сравнить от кадра к кадру, где тратится время и какие пути кода более или менее дрожат сами по себе.Но как мне это сделать?Я испытываю проблему как в Java, так и в C, и в опции Java -Xprof и в программе GCC gprof программа печатает измеренное время только от начала до конца времени жизни программы (как и ожидалось).Можно ли как-то сбрасывать и сбрасывать данные этих профилировщиков каждый цикл и / или есть ли другие профилировщики, которые я мог бы попробовать, чтобы предложить такую ​​возможность?Я также думаю, что оба они занимают всего около 100 выборок в секунду, что является очень недостаточным разрешением, когда кадр занимает всего около 10-20 мс.Есть ли способ увеличить частоту их выборки?

В качестве альтернативы, конечно - есть ли какой-то другой способ решения этих проблем?

Ответы [ 3 ]

1 голос
/ 18 января 2012

Давайте рассмотрим код C, поэтому мы не учитываем время GC.

Я предполагаю, что избыточное время происходит не в каждом кадре, но довольно редко, поэтому любой профилировщик, который просто измеряет совокупное время, неЯ расскажу вам многое.

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

Затем, когда таймер выключится, я соберу образец стека и изучу его, чтобы увидеть, что делает программа.

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

Дело в том, что вы хотите знать, что он делает, и почему он делает это в такое избыточное время.Вы надеетесь, что лишняя работа, которую он выполняет, или какая-то ее часть, происходит в это избыточное время.

Вам может потребоваться до 20 образцов, прежде чем вы увидите что-то интересное, но как только больше одногоВ примере показано, что происходит нечто, чего вы на самом деле не понимали, и вы можете от него избавиться, что значительно сократит использование лишнего времени.Чем меньше образцов вы должны взять, прежде чем увидите, тем больше будет сокращено время.

Вероятно, существует более одной такой проблемы, поэтому не останавливайтесь после того, как первое, что вы найдете.Делайте это снова и снова, пока не найдете ничего, что могли бы удалить.

0 голосов
/ 18 января 2012

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

void START_PROFILE(PROFILE_REC *rec)
{
  if (rec->frame != total_frame_count)
  {
    rec->frame = total_frame_count;
    rec->time_before_frame = rec->total_time;
  }
  rec->time_before_last = rec->total_time;
  rec->start_time = get_precise_system_time();
}
void END_PROFILE(PROFILE_REC *rec)
{
  rec->total_time += get_precise_system_time - rec->start_time;
}

Выше нет проверки ошибок, чтобы гарантировать, что END_PROFILE вызывает балансировку вызовов START_PROFILE; он будет записывать бессмысленные номера, если вызовы не совпадают, но проверка ошибок, вероятно, не будет полезна без отчетов об ошибках, а отчеты об ошибках добавят еще один уровень сложности.

Между прочим, если у вас есть задачи, которые могут выдерживать значительное дрожание в их точное время, может быть полезно в каждом кадре делать все то, что должно быть сделано в этом кадре, а затем делать то, что можно сделано "всякий раз", пока не прибудет следующий кадр. Затем обработайте этот кадр и, как только это будет сделано, вернитесь к обработке задач «всегда». В зависимости от вашего вкуса, вы можете сделать это с помощью полнофункциональной многозадачной ОС, простого циклического стекового коммутатора, набора конечных автоматов или чего угодно. Я использовал этот подход на игровом автомате с 128 байтами оперативной памяти, где «опоздание» кадра на несколько микросекунд может привести к видимому искажению изображения, и это может быть очень полезно для выравнивания колебаний времени.

0 голосов
/ 18 января 2012

Я уверен, что есть более простые способы, чем это, но вот как мы это сделали: Напишите свой собственный (очень простой) профилировщик-таймер (с макросами начала / конца), который также принимает пороговое время, и ведите его журнал, когда он проходит заданный порог.

Оберните каждый компонент верхнего уровня в игровой цикл с этим. Таким образом, вы получаете журналы о том, какие компоненты являются узкими местами.

Как только вы определили компонент, вы можете использовать тот же метод для детализации кода.

Требуется некоторая ручная работа (и, вероятно, есть инструменты, которые делают это), но очень быстро вы можете найти точное место возникновения проблемы.

Например (псевдо-код):

while (game-loop):

  START_PROFILE(PHYSICS, 0.05);
  UpdatePhysics();
  END_PROFILE(PHYSICS);

  START_PROFILE(NETWORK, 0.05);
  UpdateNetwork();
  END_PROFILE(NETWORK);

  START_PROFILE(RENDER, 0.05);
  Render();
  END_PROFILE(RENDER);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...