Как мы профилируем WebXR/WebGL приложения

В этой статье рассматриваются методы профилирования приложений WebXR и WebGL (независимо от используемого фреймворка). Профилирование — это первый шаг к оптимизации вашего приложения. Оптимизация без профилирования — это пустая трата усилий, как описано в разделе Узкие места.

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

Метрики, которые мы будем профилировать, включают время работы ЦП, время работы ГП, объем JavaScript-кучи и время загрузки приложения. Цель состоит в том, чтобы предоставить самую полную коллекцию информации по профилированию бесплатно. И нет, “количество полигонов” — это не метрика, которая нас интересует!

Содержание 

Узкие места 

Без обнаружения узкого места вашего приложения ваши усилия по оптимизации не будут эффективны.

Пример 

Представьте, что вы рендерите большую сцену с множеством объектов. Ваше приложение едва достигает 60 fps. Вы можете оптимизировать каждый объект, уменьшая количество его вершин, и всё равно оставаться на уровне 60 fps.

ЦП отвечает за отправку ГП команды для рендеринга каждого объекта (“Draw Call”). В этом случае гораздо более вероятно, что ЦП перегружен работой по отправке вызовов рендеринга, в то время как ГП просто ждёт, пока ЦП отправит больше работы!

Но мы не знаем… пока не измеряем. Именно для этого и нужно профилирование.

Типы узких мест 

Любая часть приложения может вызвать узкое место. Вот несколько примеров распространенных узких мест в порядке частоты:

Вызовы рендеринга на ЦП: Слишком много вызовов драйвера создают слишком большую нагрузку.

Мусор: Приложение работает нормально, но производит мусор, который вызывает задержки с регулярными или нерегулярными интервалами.

Логика ЦП: Ваше приложение не может быстро создать задачу для ГП. Часто вызывается тяжёлыми симуляциями физики или трассировкой лучей.

Теневой шейдинг фрагментов на ГП: Стоимость выполнения на фрагмент слишком высока.

Обработка вершин на ГП: Требования к обработке на вершине слишком высоки.

Разрешение на ГП: Вызывается пост-обработкой на мобильных ГП.

Выборка вершин на ГП: Память для вершин читается недостаточно быстро.

Каждый из них может иметь подузкие места.

Метрики 

Метрики, которые мы будем профилировать, включают:

Время работы ЦП 

Работа для любого XR приложения разделена между ЦП и ГП. ЦП отвечает за подготовку графической работы для рендеринга ГП и выполняет логику приложения.

Любой JavaScript код выполняется на ЦП для подготовки вызовов рендеринга, выполнения загрузки ресурсов, симуляции физики, рендеринга аудио и вычисления преобразований графов сцены.

Время работы ГП 

ГП отвечает за графический обработку. В то время как ЦП отправляет вызов рендеринга типа “отрендери сетку X с шейдером Y с текстурой Z и параметрами материала W”, ГП выполняет фактическую растеризацию и преобразования на вершине, чтобы создать пиксели на экране.

ГП также отвечает за отправку окончательного изображения на экран, ожидая “V-Sync”, которая синхронизирует частоту обновления экрана с частотой обновления приложения.

Частота кадров и V-Sync 

Мы не используем частоту кадров (кадров в секунду = fps) для профилирования нашего приложения.

Эта метрика упоминается только чтобы объяснить сложность понимания, как производительность взаимодействует с V-Sync. Она слишком грубая, чтобы делать полезные выводы.

Рассматривайте V-Sync как фиксированные сроки: 60, 72, 90 или более сроков в секунду (частота кадров). Мы будем называть “достижение V-Sync” рендерингом и отправкой кадра вовремя для достижения срока.

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

Если ваше приложение едва слишком медленное, это означает, что вы будете видеть, например, 30 fps вместо 59 fps, рассказывая совершенно другую историю. Возможно, ваше приложение работает довольно хорошо с 3 мс времени работы ЦП и 3 мс времени работы ГП, но из-за позднего начала работы вы пропускаете V-Sync для целевой частоты кадров, что уменьшает вашу эффективную частоту кадров вдвое. Никакое изменение количества вершин, вызовов рендеринга или классические оптимизации вам тут не помогут.

Задержка 

Задержка, время между вводом (например, движением головы) и готовым кадром, особенно важна для VR. Реализации WebXR обычно занимаются этим за нас и могут запланировать обратные вызовы кадра чуть позже, если мы не используем наш бюджет на кадр, чтобы уменьшить задержку. У нас ограниченный контроль над этим из WebXR, и поэтому мы не будем рассматривать это в этой статье.

Сборка мусора 

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

Без необходимости управления памятью вы теряете контроль над тем, когда вы хотите управлять своей памятью. Этот процесс называется “Сборка мусора” и будет происходить в случайные моменты в жизненном цикле вашего приложения – например, когда вы готовы сделать V-Sync и почти готовы отправить свой кадр.

Сборка мусора может занять от 0,1 до 10 мс. Учитывая, что ваш обычный VR бюджет кадра равен 11 мс, вы абсолютно не хотите, чтобы это происходило в случайные моменты времени.

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

Время загрузки приложения 

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

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

Оптимизируя активы и обеспечивая оптимальные настройки сервера, мы можем уменьшить время загрузки в целом.

И, используя форматы, которые требуют меньше разбора, мы можем уменьшить работу, которую ЦП должен делать после загрузки ресурсов.

Инструменты 

В этой статье мы рассмотрим следующие инструменты:

Chrome Profiler 

Встроенный профайлер Chrome позволит вам профилировать время работы JavaScript на ЦП, сборку мусора и очень приблизительно время кадра на ГП.

Вы можете найти вкладку Performance, перейдя на любой сайт и нажав Ctrl + Shift + C (Command + Shift + C на MacOS). Найдите там “Performance”. Чтобы записать сеанс профилирования, нажмите кнопку записи в верхнем левом углу (Ctrl/Command + E). Обычно достаточно 3-5 секунд, так как нас интересуют обычно одиночные кадры.

Это также работает при удалённой отладке на Android устройствах, таких как Meta Quest или ваш смартфон.

Safari имеет похожий профайлер для Mac, iOS устройств и Apple Vision PRO (например, с запущенным Safari в симуляторе Apple Vision PRO).

WebXR Profiling banner.

Chrome Memory Profiler 

В разделе Сборка мусора мы описываем подергивания в иначе плавно работающих приложениях.

Чтобы найти это узкое место, браузеры предоставляют способ сэмплировать JavaScript Heap. Вот как вы можете его включить в Chrome из описанного выше Chrome Profiler:

Как мы профилируем WebXR/WebGL приложения

Пример 

Следующим является профиль на Meta Quest 2 от Elysian, который основан на Three.js:

Как мы профилируем WebXR/WebGL приложения

Вы можете видеть, что “JS Heap” колеблется между 14.1 MB и 23.3 MB.

Когда объем памяти резко падает, мы видим, что происходит Сборка мусора. В этом случае сборка мусора настолько велика, что задерживает начало следующего кадра, вызывая его выпадение:

Как мы профилируем WebXR/WebGL приложения

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

Вкладка Сетевые подключения 

Любая вкладка сетевых подключений браузера — отличный инструмент для профилирования времени загрузки вашего приложения.

Вы можете найти вкладку “Networking”, перейдя на любой сайт и нажав Ctrl + Shift + C (Command + Shift + C на MacOS). Найдите там “Networking”. Чтобы записать сетевую активность, перезагрузите страницу с открытой вкладкой.

Блокировка загрузок 

Если ресурс не нужен немедленно при запуске приложения, он должен быть загружен позже, так как браузеры будут запускать только ограниченные параллельные запросы за раз (например, 6 для Chrome).

Как мы профилируем WebXR/WebGL приложения

HUD производительности Meta Quest 

При профилировании VR рендеринга на Meta Quest вы можете использовать Performance HUD.

Его легче всего установить через Meta Quest Developer Hub.

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

Пример 

В приведённом ниже примере Ayushman Johri показывает отличную производительность своей сцены “Vintage Study Room” на основе Wonderland Engine, используя HUD производительности Meta Quest:

OVR GPU Profiler 

Если вы столкнетесь с узкими местами вершинного или фрагментного шейдинга, вам понадобится чёткое понимание того, что занимает больше всего времени в ваших шейдерах.

ovrgpuprofiler — это очень точный инструмент. Если у вас есть некоторое понимание о памяти ГП и архитектуре, он даст вам много познаний.

Он запускается путем установки на вашу гарнитуру Meta Quest через adb и запускается через adb shell:

 1    Поддерживается 47 метрик:
 2    1       Тактов / Секунда
 3    2       % Занятость шины ГП
 4    3       % Ожидание выборки вершин
 5    4       % Ожидание выборки текстур
 6    5       Промахи L1 кэша текстур на пиксель
 7    6       % Промахов текстуры L1
 8    7       % Промахов текстуры L2
 9    8       % Ожидание системной памяти
10    9       Примитивов до обрезки/Секунда
11    10      % Примитивов, отвергнутых тривиально
12    11      % Примитивов обрезанных

(Источник: developer.oculus.com)

Пример вывода может выглядеть следующим образом:

 1$ adb shell ovrgpuprofiler -r"4,5,6"
 2
 3% Ожидание выборки текстур                      :           2.449
 4Промахи L1 кэша текстур на пиксель            :           0.124
 5% Промахов текстуры L1                          :          20.338
 6
 7% Ожидание выборки текстур                      :           2.369
 8Промахи L1 кэша текстур на пиксель            :           0.122
 9% Промахов текстуры L1                          :          20.130
10
11% Ожидание выборки текстур                      :           2.580
12Промахи L1 кэша текстур на пиксель            :           0.127
13% Промахов текстуры L1
14
15...

(Модифицировано из: developer.oculus.com)

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

L1 (“Уровень 1”) кэшевая память невероятно быстра. Чтобы убедиться, что вы её используете, убедитесь, что вы соблюдаете принцип локальности: обращайтесь к памяти, которая близка к памяти, к которой вы недавно обратились.

Spector.js 

Spector.js — это расширение для браузеров Chrome и Firefox, которое позволяет захватывать трассировки кадров WebGL. Инструмент может показывать полный список команд и суммировать такие показатели, как количество вершин и вызовов рендеринга.

Установите его из магазина расширений Chrome или из библиотеки дополнений Firefox. Вы также можете встроить инструмент через HTML тег для браузеров без поддержки плагинов.

Как мы профилируем WebXR/WebGL приложения

Сначала включите инструмент через кнопку дополнений вверху, затем запишите кадр, нажав на красную кнопку записи.

Как мы профилируем WebXR/WebGL приложения

Вы можете видеть, что Wonderland Engine рисует множество объектов всего в 11 вызовах рендеринга. В любом другом фреймворке это число будет в 10-100 раз больше.

Disjoint Timer Query 

Расширение EXT_disjoint_timer_query это расширение для WebGL, которое позволяет измерять время выполнения на ГП для набора команд WebGL. Оно имеет хорошую поддержку только в Chrome [август 2023] и если включён флаг Chrome “WebGL Debug Extensions”.

Поскольку все команды выполняются асинхронно на ГП, измерения времени также должны быть запланированы асинхронно.

Профайлер в редакторе Wonderland 

Редактор Wonderland поставляется с встроенным инструментом профилирования, который помогает понять, в чем может заключаться проблема с производительностью вашего приложения WebXR.

Как мы профилируем WebXR/WebGL приложения

Заключительные слова 

У каждого фреймворка есть свои особенности производительности. Большинство из них являются ограничениями вызова рендеринга перед чем-то другим, а затем фрагментными ограничениями — не нужно идти по пути наименьших полигонов, если не на 90 fps!

Мы разработали Wonderland Engine с нуля, чтобы избежать большинства из вышеперечисленных узких мест — это бесплатно до достижения дохода в 120 тыс. долларов США в год. Обратитесь сюда для корпоративных лицензий и поддержки.

Попробуйте Wonderland Engine сейчас и начните экономить время на оптимизации.

Как мы профилируем WebXR/WebGL приложения
Last Update: August 29, 2023

Будьте в курсе.