Вы можете настроить трассировку системы, чтобы получить профиль ЦП и потоков приложения за короткий период времени. Затем вы можете использовать отчет о трассировке системы, чтобы улучшить производительность игры.
Настройка трассировки системы на основе игры
Инструмент Systrace доступен двумя способами:
Systrace – это инструмент низкого уровня, который:
- Предоставляет фундаментальную истину. Systrace получает данные непосредственно из ядра, поэтому собранные им показатели почти идентичны тем, которые можно получить с помощью серии системных вызовов.
- Потребляет мало ресурсов. Systrace создает очень небольшую нагрузку на устройство, обычно менее 1%, поскольку передает данные в буфер в памяти.
Оптимальные настройки
Важно задать инструменту разумный набор аргументов:
- Категории. Для трассировки системы на основе игры лучше всего включить следующие категории: {
sched,freq,idle,am,wm,gfx,view,sync,binder_driver,hal,dalvik}. Размер буфера. Как правило, размер буфера в 10 МБ на ядро ЦП позволяет отслеживать данные в течение примерно 20 секунд. Например, если у устройства два четырехъядерных процессора (всего восемь ядер), то подходящее значение для передачи в программу
systrace– 80 000 КБ (80 МБ).Если в игре часто происходит переключение контекста, увеличьте буфер до 15 МБ на ядро процессора.
Специальные события. Если вы определили специальные события для отслеживания в игре, включите флаг
-a. Он позволит Systrace добавлять эти события в выходной отчет.
Если вы используете программу командной строки systrace, введите следующую команду, чтобы записать трассировку системы с учетом рекомендаций по набору категорий, размеру буфера и специальным событиям:
python systrace.py -a com.example.myapp -b 80000 -o my_systrace_report.html \ sched freq idle am wm gfx view sync binder_driver hal dalvik
Если вы используете системное приложение Systrace на устройстве, выполните следующие действия, чтобы записать трассировку системы, в которой применяются рекомендации по набору категорий, размеру буфера и специальным событиям:
Включите параметр Отслеживать приложения, доступные для отладки.
Чтобы использовать эту настройку, на устройстве должно быть доступно 256 или 512 МБ памяти (в зависимости от того, сколько ядер у ЦП – 4 или 8), и каждый фрагмент памяти размером 64 МБ должен быть доступен как непрерывный блок.
Выберите Категории и включите категории из списка ниже.
am: менеджер действийbinder_driver: драйвер ядра Binderdalvik: Dalvik VMfreq: частота ЦПgfx: графикаhal: аппаратные модулиidle: бездействие ЦПsched: планирование ЦПsync: синхронизацияview: просмотр системыwm: менеджер окон
Включите параметр Запись трассировки.
Загрузите игру.
Выполните в игре действия, соответствующие игровому процессу, эффективность которого вы хотите измерить.
Как только вы заметите в игре нежелательное поведение, отключите системную трассировку.
Вы собрали статистику производительности, необходимую для дальнейшего анализа проблемы.
Чтобы сэкономить место на диске, трассировки системы на устройстве сохраняют файлы в сжатом формате трассировки (*.ctrace). Чтобы распаковать этот файл при создании отчета, используйте программу командной строки и включите параметр --from-file:
python systrace.py --from-file=/data/local/traces/my_game_trace.ctrace \ -o my_systrace_report.html
Как улучшить определенные показатели эффективности
В этом разделе рассказывается о распространенных проблемах с производительностью в мобильных играх и о том, как их выявлять и устранять.
Скорость загрузки
Игроки хотят как можно быстрее приступить к игре, поэтому важно максимально сократить время загрузки. Обычно помогают следующие меры:
- Используйте отложенную загрузку. Если вы используете одни и те же объекты в нескольких сценах или уровнях игры, загружайте их только один раз.
- Уменьшите размер ресурсов. Таким образом вы сможете добавить в APK-файл игры несжатые версии этих объектов.
- Используйте эффективный метод сжатия. Примером такого метода является zlib.
- Используйте IL2CPP вместо mono. (только если вы используете Unity). IL2CPP обеспечивает более высокую производительность выполнения скриптов C#.
- Сделайте игру многопоточной. Подробнее о постоянстве частоты кадров…
Согласованность частоты кадров
Один из важнейших элементов игрового процесса – стабильная частота кадров. Чтобы достичь этой цели было проще, следуйте рекомендациям по оптимизации, приведенным в этом разделе.
Многопоточность
При разработке для нескольких платформ все действия в игре обычно выполняются в одном потоке. Хотя этот метод выполнения легко реализовать во многих игровых движках, он далек от оптимального при работе на устройствах Android. В результате однопоточные игры часто загружаются медленно и не обеспечивают стабильную частоту кадров.
На рисунке 1 показан Systrace, типичный для игры, работающей только на одном ЦП:
Чтобы повысить производительность игры, сделайте ее многопоточной. Обычно лучше всего использовать две нити:
- Игровой поток, который содержит основные модули игры и отправляет команды отрисовки.
- Поток отрисовки, который получает команды отрисовки и преобразует их в графические команды, которые графический процессор устройства может использовать для отображения сцены.
Vulkan API расширяет эту модель, поскольку позволяет одновременно передавать два общих буфера. Эта функция позволяет распределять несколько потоков отрисовки между несколькими процессорами, что сокращает время отрисовки сцены.
Вы также можете внести в движок изменения, которые повысят производительность многопоточности в игре:
- Если вы разрабатываете игру с помощью игрового движка Unity, включите параметры Многопотоковая отрисовка и Скиннинг на GPU.
- Если вы используете собственный движок рендеринга, убедитесь, что конвейер команд рендеринга и конвейер графических команд правильно согласованы. В противном случае могут возникнуть задержки при отображении сцен игры.
После применения этих изменений игра должна занимать не менее двух процессоров одновременно, как показано на рисунке 2:
Загрузка элементов интерфейса
При создании многофункциональной игры возникает соблазн показывать игроку множество разных вариантов и действий одновременно. Чтобы поддерживать стабильную частоту кадров, учитывайте относительно небольшой размер экранов мобильных устройств и сделайте интерфейс как можно проще.
На рисунке 3 показан отчет Systrace, в котором представлен пример кадра интерфейса, пытающегося отрисовать слишком много элементов относительно возможностей мобильного устройства.
Рекомендуется сократить время обновления интерфейса до 2–3 миллисекунд. Чтобы быстро обновлять данные, выполняйте оптимизацию, похожую на описанную ниже.
- Обновлять только те элементы на экране, которые переместились.
- Ограничьте количество текстур и слоев интерфейса. Попробуйте объединить вызовы графики, например шейдеров и текстур, которые используют один и тот же материал.
- Передавать операции анимации элементов на графический процессор.
- Более агрессивное отсечение по пирамиде видимости и окклюзии.
- Если возможно, выполняйте операции рисования с помощью Vulkan API. При использовании Vulkan нагрузка на вызовы отрисовки ниже.
Потребляемая мощность
Даже после оптимизации, описанной в предыдущем разделе, вы можете заметить, что частота кадров в игре снижается в первые 45–50 минут игрового процесса. Кроме того, со временем устройство может начать нагреваться и потреблять больше энергии.
Во многих случаях нежелательный нагрев и энергопотребление связаны с тем, как рабочая нагрузка от игры распределяется между ЦП устройства. Чтобы повысить энергоэффективность игры, следуйте рекомендациям, приведенным в следующих разделах.
Как закрепить потоки, потребляющие много памяти, за одним ЦП
На многих мобильных устройствах кеш L1 находится на определенных процессорах, а кеш L2 – на наборе процессоров, которые используют один и тот же тактовый генератор. Чтобы увеличить количество совпадений в кеше L1, лучше всего запускать основной поток игры и другие потоки, интенсивно использующие память, на одном ЦП.
Откладывать выполнение задач, требующих небольших затрат времени, на менее мощных процессорах.
Большинство игровых движков, в том числе Unity, откладывают операции рабочего потока на другой ЦП относительно основного потока игры. Однако движок не знает архитектуру устройства и не может предсказать нагрузку на игру так же хорошо, как вы.
Большинство систем на чипе имеют по крайней мере два общих тактовых генератора: один для быстрых ЦП устройства, а другой – для медленных ЦП. Из-за такой архитектуры, если одному быстрому ЦП нужно работать на максимальной скорости, все остальные быстрые ЦП также работают на максимальной скорости.
На примере отчета, показанного на рисунке 4, приведена игра, в которой используются быстрые процессоры. Однако при такой высокой активности быстро выделяется большое количество энергии и тепла.
Чтобы снизить общее энергопотребление, лучше всего предложить планировщику отложить выполнение задач с меньшей продолжительностью, таких как загрузка аудио, запуск рабочих потоков и выполнение хореографа, на медленных процессорах устройства. Перенесите как можно больше этой работы на медленные процессоры, сохранив при этом желаемую частоту кадров.
На большинстве устройств сначала указываются медленные процессоры, а затем быстрые, но нельзя быть уверенным, что в вашем устройстве используется именно такой порядок. Чтобы проверить это, выполните команды, похожие на те, которые приведены в этом коде обнаружения топологии ЦП на GitHub.
Определив, какие процессоры работают медленнее, вы можете задать для коротких потоков привязку, которую будет учитывать планировщик устройства. Для этого добавьте следующий код в каждый поток:
#include <sched.h> #include <sys/types.h> #include <unistd.h> pid_t my_pid; // PID of the process containing your thread. // Assumes that cpu0, cpu1, cpu2, and cpu3 are the "slow CPUs". cpu_set_t my_cpu_set; CPU_ZERO(&my_cpu_set); CPU_SET(0, &my_cpu_set); CPU_SET(1, &my_cpu_set); CPU_SET(2, &my_cpu_set); CPU_SET(3, &my_cpu_set); sched_setaffinity(my_pid, sizeof(cpu_set_t), &my_cpu_set);
Тепловой стресс
Когда устройства перегреваются, они могут снижать частоту ЦП и/или графического процессора, и это может неожиданным образом повлиять на игры. Проблемы чаще возникают в играх со сложной графикой, требующих больших вычислительных ресурсов или постоянной сетевой активности.
Используйте Thermal API, чтобы отслеживать изменения температуры на устройстве и принимать меры для снижения энергопотребления и температуры устройства. Когда устройство сообщает о перегреве, приостановите текущие действия, чтобы снизить энергопотребление. Например, уменьшите частоту кадров или тесселяцию полигонов.
Сначала объявите объект PowerManager и инициализируйте его в методе onCreate(). Добавьте к объекту прослушиватель статуса нагрева.
Kotlin
class MainActivity : AppCompatActivity() { lateinit var powerManager: PowerManager override fun onCreate(savedInstanceState: Bundle?) { powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager powerManager.addThermalStatusListener(thermalListener) } }
Java
public class MainActivity extends AppCompatActivity { PowerManager powerManager; @Override protected void onCreate(Bundle savedInstanceState) { ... powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); powerManager.addThermalStatusListener(thermalListener); } }
Определите действия, которые будут выполняться при обнаружении прослушивателем изменения статуса. Если в игре используется код на C/C++, добавьте код в уровни теплового статуса в onThermalStatusChanged(), чтобы вызывать код игры с помощью JNI или использовать нативный Thermal API.
Kotlin
val thermalListener = object : PowerManager.OnThermalStatusChangedListener() { override fun onThermalStatusChanged(status: Int) { when (status) { PowerManager.THERMAL_STATUS_NONE -> { // No thermal status, so no action necessary } PowerManager.THERMAL_STATUS_LIGHT -> { // Add code to handle light thermal increase } PowerManager.THERMAL_STATUS_MODERATE -> { // Add code to handle moderate thermal increase } PowerManager.THERMAL_STATUS_SEVERE -> { // Add code to handle severe thermal increase } PowerManager.THERMAL_STATUS_CRITICAL -> { // Add code to handle critical thermal increase } PowerManager.THERMAL_STATUS_EMERGENCY -> { // Add code to handle emergency thermal increase } PowerManager.THERMAL_STATUS_SHUTDOWN -> { // Add code to handle immediate shutdown } } } }
Java
PowerManager.OnThermalStatusChangedListener thermalListener = new PowerManager.OnThermalStatusChangedListener () { @Override public void onThermalStatusChanged(int status) { switch (status) { case PowerManager.THERMAL_STATUS_NONE: // No thermal status, so no action necessary break; case PowerManager.THERMAL_STATUS_LIGHT: // Add code to handle light thermal increase break; case PowerManager.THERMAL_STATUS_MODERATE: // Add code to handle moderate thermal increase break; case PowerManager.THERMAL_STATUS_SEVERE: // Add code to handle severe thermal increase break; case PowerManager.THERMAL_STATUS_CRITICAL: // Add code to handle critical thermal increase break; case PowerManager.THERMAL_STATUS_EMERGENCY: // Add code to handle emergency thermal increase break; case PowerManager.THERMAL_STATUS_SHUTDOWN: // Add code to handle immediate shutdown break; } } };
Задержка между касанием и реакцией экрана
В играх, где кадры отрисовываются как можно быстрее, возникает ситуация, когда ресурсы графического процессора используются на пределе возможностей и буфер кадров переполняется. ЦП должен ждать графический процессор, что приводит к заметной задержке между вводом игрока и отображением этого ввода на экране.
Чтобы определить, можно ли улучшить темп смены кадров в вашей игре, выполните следующие действия:
- Создайте отчет Systrace, в который входят категории
gfxиinput. Эти категории особенно полезны для определения времени реакции на касание. Проверьте раздел
SurfaceViewв отчете Systrace. Переполненный буфер приводит к тому, что количество ожидающих отрисовки буферов колеблется между 1 и 2, как показано на рисунке 5:Рисунок 5. Отчет Systrace, в котором показан переполненный буфер, периодически не принимающий команды рисования.
Чтобы устранить эту проблему, выполните действия, описанные в следующих разделах:
Как интегрировать Android Frame Pacing API в игру
Android Frame Pacing API позволяет выполнять переключение кадров и задавать интервал переключения таким образом, чтобы в игре поддерживалась более стабильная частота кадров.
Уменьшите разрешение объектов игры, не относящихся к интерфейсу
На современных мобильных устройствах гораздо больше пикселей, чем может обработать проигрыватель, поэтому можно уменьшить разрешение так, чтобы пять или даже десять пикселей одного цвета были представлены одним. Учитывая структуру большинства медийных кешей, лучше уменьшать разрешение только по одной стороне.
Однако не уменьшайте разрешение элементов интерфейса. Важно сохранить толщину линий этих элементов, чтобы размер области прикосновения был достаточно большим для всех игроков.
Плавность отрисовки
Когда SurfaceFlinger захватывает буфер дисплея, чтобы показать сцену в игре, активность ЦП на мгновение возрастает. Если эти пики активности ЦП происходят неравномерно, в игре могут наблюдаться перебои. На диаграмме на рисунке 6 показано, почему это происходит:
Если кадр начинает отрисовываться слишком поздно, даже на несколько миллисекунд, он может не успеть к следующему окну показа. Затем кадр должен дождаться следующей вертикальной синхронизации, чтобы быть показанным (33 миллисекунды при игре с частотой 30 кадров в секунду), что приводит к заметной задержке с точки зрения игрока.
Чтобы избежать этого, используйте Android Frame Pacing API, который всегда представляет новый кадр на фронте VSync.
Состояние памяти
Если игра запущена в течение длительного времени, на устройстве может возникнуть ошибка нехватки памяти.
В этом случае проверьте активность ЦП в отчете Systrace и узнайте, как часто система вызывает демон kswapd. Если во время выполнения игры происходит много вызовов, лучше проверить, как игра управляет памятью и очищает ее.
Подробнее о том, как управлять памятью…
Состояние цепочки
При просмотре типичных элементов отчета Systrace можно узнать, сколько времени определенный поток провел в каждом возможном состоянии потока. Для этого выберите поток в отчете, как показано на рисунке 7:
Как показано на рисунке 7, может оказаться, что потоки вашей игры не так часто находятся в состоянии "выполняется" или "готов к выполнению", как должны. Ниже перечислены распространенные причины, по которым поток может периодически переходить в необычное состояние:
- Если поток находится в спящем режиме в течение длительного времени, это может быть связано с конфликтом блокировок или ожиданием активности графического процессора.
- Если поток постоянно блокируется при I/O, вы либо считываете слишком много данных с диска за раз, либо в игре происходит пробуксовка.
Дополнительные ресурсы
Чтобы узнать больше о том, как повысить производительность игры, ознакомьтесь со следующими дополнительными ресурсами:
Видео
- Презентация Systrace for Games с саммита для разработчиков игр Android 2018 г.