Если вы не можете это измерить, вы не сможете это улучшить.
Лорд Кельвин
Чтобы ваши HTML5-игры работали быстрее, вам необходимо сначала определить узкие места в производительности, но это может быть сложно. Оценка данных о количестве кадров в секунду (FPS) — это только начало, но чтобы увидеть полную картину, вам необходимо уловить нюансы в деятельности Chrome.
Инструмент about:tracing
предоставляет информацию, которая поможет вам избежать поспешных обходных решений, направленных на повышение производительности, но которые по сути являются догадками из лучших побуждений. Вы сэкономите много времени и энергии, получите более четкое представление о том, что Chrome делает с каждым кадром, и будете использовать эту информацию для оптимизации своей игры.
Привет about:tracing
Инструмент Chrome about:tracing
дает вам представление обо всех действиях Chrome за определенный период времени с такой степенью детализации, что поначалу это может показаться вам ошеломляющим. Многие функции Chrome готовы к трассировке, поэтому без каких-либо ручных инструментов вы все равно можете использовать about:tracing
для отслеживания своей производительности. (См. следующий раздел о ручном инструментировании вашего JS)
Чтобы просмотреть представление трассировки, просто введите «about:tracing» в омнибокс Chrome (адресную строку).
С помощью инструмента трассировки вы можете начать запись, запустить игру на несколько секунд, а затем просмотреть данные трассировки. Вот пример того, как могут выглядеть данные:
Да, это сбивает с толку. Давайте поговорим о том, как это читать.
Каждая строка представляет профилируемый процесс, ось слева направо указывает время, а каждый цветной прямоугольник представляет собой инструментированный вызов функции. Существуют строки для различных типов ресурсов. Наиболее интересными для профилирования игр являются CrGpuMain, который показывает, что делает графический процессор (GPU), и CrRendererMain. Каждая трассировка содержит строки CrRendererMain для каждой открытой вкладки в течение периода трассировки (включая саму вкладку about:tracing
).
При чтении данных трассировки ваша первая задача — определить, какая строка CrRendererMain соответствует вашей игре.
В этом примере двумя кандидатами являются: 2216 и 6516. К сожалению, в настоящее время не существует четкого способа выбрать ваше приложение, кроме поиска строки, которая выполняет много периодических обновлений (или если вы вручную оснастили свой код с помощью точки трассировки, чтобы найти строку, содержащую данные трассировки). В этом примере похоже, что 6516 выполняет основной цикл от частоты обновлений. Если вы закроете все остальные вкладки перед запуском трассировки, найти правильный CrRendererMain будет проще. Но все равно могут быть строки CrRendererMain для процессов, отличных от вашей игры.
Находим свой кадр
После того, как вы нашли нужную строку в инструменте трассировки для своей игры, следующим шагом будет поиск основного цикла. Основной цикл выглядит как повторяющийся шаблон в данных трассировки. Вы можете перемещаться по данным трассировки, используя клавиши W, A, S, D: A и D для перемещения влево или вправо (назад и вперед во времени) и W и S для увеличения и уменьшения масштаба данных. Вы ожидаете, что ваш основной цикл будет повторяться каждые 16 миллисекунд, если ваша игра работает на частоте 60 Гц.
Как только вы определите пульс вашей игры, вы сможете понять, что именно ваш код делает в каждом кадре. Используйте W, A, S, D для увеличения масштаба, пока не сможете прочитать текст в функциональных полях.
Эта коллекция полей показывает серию вызовов функций, каждый вызов представлен цветным прямоугольником. Каждая функция вызывалась полем над ней, поэтому в этом случае вы можете видеть, что MessageLoop::RunTask вызвал RenderWidget::OnSwapBuffersComplete, который, в свою очередь, вызвал RenderWidget::DoDeferredUpdate и так далее. Читая эти данные, вы можете получить полное представление о том, что что называется и сколько времени заняло каждое выполнение.
Но здесь все становится немного липким. Информация, предоставляемая about:tracing
представляет собой необработанные вызовы функций из исходного кода Chrome. По названию можно сделать обоснованные предположения о том, что делает каждая функция, но эта информация не совсем удобна для пользователя. Полезно видеть общий поток вашего кадра, но вам нужно что-то более понятное для человека, чтобы действительно понять, что происходит.
Добавление тегов трассировки
К счастью, есть удобный способ добавить в код ручные инструменты для создания данных трассировки: console.time
и console.timeEnd
.
console.time("update");
update();
console.timeEnd("update");
console.time("render");
update();
console.timeEnd("render");
Приведенный выше код создает новые поля в имени представления трассировки с указанными тегами, поэтому, если вы перезапустите приложение, вы увидите поля «обновление» и «рендеринг», которые показывают время, прошедшее между начальным и конечным вызовами для каждого тега. .
Используя это, вы можете создавать удобочитаемые данные трассировки для отслеживания горячих точек в вашем коде.
Графический процессор или процессор?
При использовании графики с аппаратным ускорением один из наиболее важных вопросов, который вы можете задать во время профилирования: привязан ли этот код к графическому процессору или к процессору? С каждым кадром вы будете выполнять некоторую работу по рендерингу на графическом процессоре и некоторую логику на центральном процессоре; Чтобы понять, что замедляет вашу игру, вам нужно увидеть, как работа балансируется между двумя ресурсами.
Сначала найдите в представлении трассировки строку с именем CrGPUMain, которая указывает, занят ли графический процессор в определенное время.
Вы можете видеть, что каждый кадр вашей игры вызывает работу процессора как в CrRendererMain, так и в графическом процессоре. Приведенная выше трассировка показывает очень простой вариант использования, когда и ЦП, и графический процессор простаивают большую часть каждого 16-миллисекундного кадра.
Представление трассировки действительно становится полезным, когда у вас игра работает медленно, и вы не уверены, какой ресурс вы максимально используете. Наблюдение за тем, как связаны линии графического процессора и процессора, является ключом к отладке. Возьмите тот же пример, что и раньше, но добавьте немного дополнительной работы в цикл обновления.
console.time("update");
doExtraWork();
update(Math.min(50, now - time));
console.timeEnd("update");
console.time("render");
render();
console.timeEnd("render");
Теперь вы увидите след, который выглядит следующим образом:
О чем говорит нам этот след? Мы видим, что изображенный кадр изменяется от 2270 мс до 2320 мс, а это означает, что каждый кадр занимает около 50 мс (частота кадров 20 Гц). Рядом с полем обновления можно увидеть полоски цветных прямоугольников, представляющих функцию рендеринга, но в кадре полностью доминирует само обновление.
В отличие от того, что происходит с процессором, вы можете видеть, что графический процессор по-прежнему простаивает большую часть каждого кадра. Чтобы оптимизировать этот код, вы можете найти операции, которые можно выполнить в коде шейдера, и переместить их в графический процессор, чтобы максимально эффективно использовать ресурсы.
А как насчет того, когда сам код шейдера работает медленно, а графический процессор перегружен? Что, если мы удалим ненужную работу из ЦП и вместо этого добавим немного работы в код фрагментного шейдера. Вот неоправданно дорогой фрагментный шейдер:
#ifdef GL_ES
precision highp float;
#endif
void main(void) {
for(int i=0; i<9999; i++) {
gl_FragColor = vec4(1.0, 0, 0, 1.0);
}
}
Как выглядит след кода, использующего этот шейдер?
Опять же, обратите внимание на продолжительность кадра. Здесь повторяющийся шаблон варьируется примерно от 2750 мс до 2950 мс, продолжительность 200 мс (частота кадров около 5 Гц). Строка CrRendererMain почти полностью пуста, что означает, что процессор большую часть времени простаивает, а графический процессор перегружен. Это верный признак того, что ваши шейдеры слишком тяжелые.
Если вы не знали, что именно вызывает низкую частоту кадров, вы могли наблюдать обновление 5 Гц и испытывать искушение зайти в код игры и попытаться оптимизировать или удалить игровую логику. В данном случае это не принесет абсолютно никакой пользы, потому что логика в игровом цикле — это не то, что съедает время. Фактически, эта трассировка указывает на то, что выполнение большей работы ЦП в каждом кадре будет по существу «бесплатным», поскольку ЦП простаивает, поэтому увеличение нагрузки не повлияет на продолжительность кадра.
Реальные примеры
Теперь давайте проверим, как выглядят данные трассировки из реальной игры. Одна из замечательных особенностей игр, созданных с использованием открытых веб-технологий, заключается в том, что вы можете видеть, что происходит в ваших любимых продуктах. Если вы хотите протестировать инструменты профилирования, вы можете выбрать свою любимую игру WebGL в Интернет-магазине Chrome и профилировать ее с помощью about:tracing
. Это пример трассировки, взятый из превосходной игры Skid Racer на WebGL.
Похоже, каждый кадр занимает около 20 мс, а это значит, что частота кадров составляет около 50 FPS. Вы можете видеть, что работа между процессором и графическим процессором сбалансирована, но графический процессор — это наиболее востребованный ресурс. Если вы хотите увидеть, каково это — создавать реальные примеры игр WebGL, попробуйте поиграть с некоторыми играми из Интернет-магазина Chrome, созданными с помощью WebGL, включая:
- Скид-рейсер
- Надувная мышь
- Украшенный драгоценностями
- Филдраннерс
- Злые птицы
- Деревня Жуков
- Монстр Дэш
Заключение
Если вы хотите, чтобы ваша игра работала с частотой 60 Гц, то для каждого кадра все ваши операции должны укладываться в 16 мс процессорного времени и 16 мс графического процессора. У вас есть два ресурса, которые можно использовать параллельно, и вы можете переносить работу между ними, чтобы максимизировать производительность. Представление about:tracing
в Chrome — бесценный инструмент для понимания того, что на самом деле делает ваш код, и поможет вам максимизировать время разработки, решая правильные проблемы.
Что дальше?
Помимо графического процессора, вы также можете отслеживать другие части среды выполнения Chrome. Chrome Canary, ранняя версия Chrome, предназначена для отслеживания операций ввода-вывода, IndexedDB и ряда других действий. Вам следует прочитать эту статью о Chromium, чтобы лучше понять текущее состояние трассировки событий.
Если вы разработчик веб-игр, обязательно посмотрите видео ниже. Это презентация команды Google Game Developer Advocate на GDC 2012 об оптимизации производительности игр Chrome: