То, что не поддаётся измерению, не поддаётся и улучшению.
— Лорд Кельвин
Чтобы ускорить игры, написанные при помощи HTML5, для начала нужно определить их узкие места. Подсчёт FPS – это неплохой метод, но чтобы увидеть полную информацию, необходимо разобраться в нюансах поведения Chrome.
Инструмент about:tracing позволяет избежать лишней работы, связанной с увеличением быстродействия, и основанной большей частью на догадках. Вы сэкономите энергию и деньги, если чётко проследите работу браузера при помощи этого инструмента.
Он показывает вам всё, что делает Chrome, причём настолько детально, что сперва это даже может ошеломить. Многие функции Chrome изначально предназначены для трассировки, поэтому прямо из коробки для оценки производительности можно использовать about:tracing.
Для этого просто напишите about:tracing в адресной строке.
Инструмент трассировки позволяет включить запись, запустить игру на несколько секунд и посмотреть данные трассировки. Пример того, как они могут выглядеть:
Да, сначала выглядит запутанно.
Каждый ряд – процесс, который мы отслеживаем. Ось слева направо обозначает время, а цветной прямоугольник – вызов функции. Ряды представлены для самых разных ресурсов, но больше всего нас интересует CrGpuMain, показывающий работу GPU, и CrRendererMain. В каждой трассировке есть строки CrRendererMain для каждой открытой вкладки (включая саму вкладку about:tracing).
Первая ваша задача – определить, какой ряд CrRendererMain соответствует игре.
В этом примере есть два кандидата – 2216 и 6516. К сожалению, пока нет лёгкого способа выбрать ваше приложение, кроме как поискать ту линию, которая периодически выполняет обновления (или, если вы изучаете код с помощью точек отслеживания – линию, содержащую данные по отслеживанию). В нашем примере видно, что 6516 выполняет много обновлений. Если закрыть все лишние вкладки, то процесс поиска станет попроще. Но всё равно сюда могут попасть ряды CrRendererMain от других процессов.
Ищем кадр
Найдя нужный ряд, нужно приступать к поиску главного цикла. Она выглядит как повторяющийся шаблон. Гулять по данным отслеживания можно при помощи кнопок WASD – A и D позволяют двигаться влево и вправо (назад и вперёд по времени), W и S – увеличивать и уменьшать картинку. Главный цикл должен повторяться каждые 16 мс, если ваша игра идёт на 60 Гц.
Найдя ваши «биения сердца», можно начинать подробное исследование деятельности кода. Используйте W, A, S, D для увеличения картинки, пока на ней не станет виден текст.
Прямоугольники показывают последовательность вызовов функций, каждый из которых представлен своим цветным прямоугольником. Каждая функция была вызвана из верхнего прямоугольника, так что в нашем случае видно, что MessageLoop::RunTask вызвала RenderWidget::OnSwapBuffersComplete, которая вызвала RenderWidget::DoDeferredUpdate, и так далее. Так и получается полная картина происходящего.
Правда, эта информация – это «сырые» вызовы из исходного кода Chrome. Конечно, можно догадываться, что делает каждая из функций, но информация не особенно удобная. Требуется что-то более человеко-читаемое.
Добавляем метки отслеживания
И такая возможность существует — это console.time и console.timeEnd.
console.time("update");
update();
console.timeEnd("update");
console.time("render");
update();
console.timeEnd("render");
Такой код создаёт новые прямоугольники с заданными именами, поэтому вы увидите в отчёте «update» и «render», которые покажут, сколько времени прошло между вызовами этих меток.
GPU или CPU?
Когда у вас есть графический ускоритель, то один из главных вопросов профилирования – что перегружает наш код, GPU или CPU? Каждый кадр отнимает ресурсы как у одного, так и у другого. Чтобы понять, где скрываются тормоза, нужно посмотреть на баланс их работы.
Найдите строку по имени CrGPUMain, где показана занятость GPU.
Видно, что в каждом кадре работают как CPU в CrRendererMain, так и GPU. На картинке показан очень простой случай, когда оба процессора простаивают большую часть времени.
Этот график становится полезен, когда ваша игра начинает реально тормозить, а вы не уверены, какого из ресурсов ей не хватает. Возьмём предыдущий пример, и добавим в него нагрузки для цикла update.
console.time("update");
doExtraWork();
update(Math.min(50, now - time));
console.timeEnd("update");
console.time("render");
render();
console.timeEnd("render");
Теперь трассировка выглядит так:
Что это значит? Кадр занимает по времени от момента 2270ms до момента 2320ms, то есть каждый из них рендерится по 50 мс (частота 20 Гц). Длинные узкие полоски представляют функцию рендера рядом с прямоугольником update, но в основном всё время занято самой функцией update.
При этом видно, что GPU почти всё время отдыхает. Для оптимизации кода надо искать те операции, которые можно сделать через шейдеры, и отдать их на GPU.
А что, если код шейдеров будет тормозить и перегрузит GPU? Давайте удалим ненужную работу с CPU и добавим работу шейдерам. Вот вам пример бесполезной загрузки шейдера:
#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 почти пустая, то есть CPU отдыхает, а GPU перегружен. Это однозначный признак перегруженности шейдеров.
Жизненные примеры
Проверим, как выглядят данные отслеживания реальной игры. Одна из замечательных черт открытых технологий состоит в том, что можно покопаться в ваших любимых продуктах. Можно взять любую игру на WebGL из Chrome Web Store и профилировать её через about:tracing. Перед вами пример трассировки игры Skid Racer:
Судя по всему, каждый кадр рендерится 20 мс, то есть частота составляет порядка 50 Гц. Видно, как сбалансирована работа между CPU и GPU, но GPU работает больше. Попробуйте на реальных примерах изучить трассировку следующих продуктов из Chrome Web Store:
Заключение
Если вы хотите, чтобы ваша игра шла с FPS 60 Гц, каждый её кадр должен создаваться не более, чем за 16 мс как времени CPU, так и GPU. Два этих ресурса можно задействовать параллельно, и для максимизации производительности работу можно делить между ними.
Что дальше?
Кроме GPU, можно отследить и другие компоненты Chrome. Прочтите статью про Chromium, чтобы лучше разобраться в текущих возможностях трассировки Chrome.
Разработчикам игр на WebGL рекомендую посмотреть следующее видео – это презентация команды Google's Game Developer Advocate с GDC 2012 по поводу оптимизации производительности игр для Chrome:
TheRabbitFlash
Простите, но чтоб игра шла 60fps — она должна быть standalone или не на JS
rPman
А я согласен с комментарием… но не в этой формулировке!
В невысокой скорости работы приложений виновато чаще не сам JS, который вполне себе быстрый, а весь браузер и инфраструктура.
Deamon87
И еще есть ANGLE прослойка. Если её отключить, то производительность начинает стремится к standalone приложениям
TheRabbitFlash
Вы меня извините, но если что-то отключать приходится, ковыряться в настройках и доводить напильником — то смысла в «без плагина работает» просто нет.
Давайте все же думать как end user, а не читатель хабра, который, в общей массе, живет в командой строке линукса :)
Приходил дедушка сегодня и сказал, что у него перестала работать игра. Пока я ему объяснял что надо настроить — он сказал умную вещь «что мешает что-то поставить, чтоб оно не требовало настроек и не тормозило?». Человек не знает таких слов, как Flash, WebGL, HTML5. Он смартфон только на ТВ видел и ему пофигу на безопасность и т.д… Он просто хочет, чтоб работало у него так же, как и у меня :)