Анализ с помощью профилирования отрисовки графического процессора

Инструмент Профилирование отрисовки GPU показывает относительное время, которое требуется каждому этапу конвейера рендеринга для отрисовки предыдущего кадра. Эти знания помогут вам выявить узкие места в конвейере и оптимизировать производительность рендеринга приложения.

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

Визуальное представление

Инструмент "Профилирование отрисовки GPU" показывает этапы и их относительную продолжительность в виде графика – цветной гистограммы. Пример такого экрана показан на рисунке 1.

График отрисовки графического процессора
Рисунок 1. График отрисовки графического процессора

Каждый сегмент вертикальной полосы на графике "Профилирование отрисовки GPU" представляет собой этап конвейера и выделен определенным цветом. На рисунке 2 показано, что означает каждый цвет.

Легенда графика отрисовки графического процессора
Рисунок 2. Обозначения на графике профилирования графического процессора

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

Этапы и их значения

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

Обработка ввода

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

Когда этот сегмент большой

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

Прокрутка LazyColumn или LazyRow также может происходить на этом этапе. Как только касание пользователя квалифицируется как прокрутка, ленивый список использует события касания для динамического составления и размещения элементов. Если ваше приложение выполняет пользовательские действия, реагируя на изменения положения прокрутки, важно сделать эту операцию как можно быстрее, чтобы предотвратить пропуск кадров. Для дальнейшего анализа можно использовать инструменты профилирования, такие как Профилировщик ЦП в Android Studio или Perfetto. Подробнее о трассировке системы…

Анимации

На этапе анимации показано, сколько времени потребовалось для оценки всех состояний анимации, которые выполнялись в этом кадре. Вот некоторые распространенные API анимации в Compose: animate*AsState, Transition и Animatable. Кроме того, на этом этапе запускается Recomposer, который обрабатывает изменения состояния снимка и обновляет композиции. Это означает, что накладные расходы на рекомпозицию часто возникают непосредственно на этапе анимации.

Для интерфейсов Jetpack Compose добавьте библиотеку Compose Runtime Tracing, чтобы видеть подробные трассировки композиции наряду с системными событиями.

Когда этот сегмент большой

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

Измерить

Чтобы нарисовать composable-функции на экране, Android выполняет три этапа для узлов макета в дереве пользовательского интерфейса.

Сначала система измеряет узлы макета. У каждой composable-функции есть определенные ограничения и модификаторы, которые описывают предельные размеры объекта на экране. Некоторые composable-функции могут иметь определенный фиксированный размер, другие – размер, который адаптируется к ограничениям, передаваемым родительским контейнером макета.

Затем система размещает узлы макета. После того как Compose рассчитает размеры дочерних узлов на этапе измерения, он может перейти к этапу размещения, на котором он определяет размеры и размещает узлы макета на экране.

Для повышения эффективности система всегда выполняет этот однопроходный макет. Когда макет composable-функции становится недействительным, Compose измеряет этот узел и передает обновления макета родительским иерархиям, только если дочерний узел меняет свой размер или ограничения.

Когда этот сегмент большой

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

Используйте Профилировщик ЦП в Android Studio или Perfetto, чтобы проверить проходы макета и выявить узкие места. Подробнее о трассировке системы…

Рисование

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

На панели Draw (Рисование) показано, сколько времени уходит на запись команд в список отображения для всех узлов макета, которые нужно обновить на экране для этого кадра. Измеренное время также относится к любой пользовательской логике рисования, которая может быть внутри модификаторов рисования или composable-функции Canvas.

Когда этот сегмент большой

Проще говоря, этот показатель отражает, сколько времени потребовалось на выполнение всех команд отрисовки для каждого недействительного узла макета. В это время входит и то, которое уходит на отправку команд дочерним узлам и объектам VectorDrawable. Поэтому, когда вы видите этот всплеск, причиной может быть то, что многие composable-функции внезапно стали недействительными. В результате приходится заново выполнять команды рисования и генерировать списки отображения узлов макета. Или же у вас есть несколько пользовательских composable-функций или холстов с очень сложной логикой в реализации DrawScope.

Кроме того, Compose часто обрабатывает свои внутренние проходы измерения и макета в рамках того, что платформа считает фазой рисования. Следовательно, высокий столбец Draw может быть вызван не только командами рисования, но и ресурсоемкими или избыточными внутренними операциями измерения/макета. Если вы сомневаетесь, запишите трассировку Perfetto, чтобы увидеть, связаны ли накладные расходы с процедурами рисования или проходами измерения и компоновки Compose.

Загрузить

Показатель загрузки отражает время, которое требуется для передачи объектов битовых карт из памяти ЦП в память графического процессора во время текущего кадра.

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

Примечание. На устройствах с Lollipop этот этап фиолетовый.

Когда этот сегмент большой

Все ресурсы для кадра должны находиться в памяти графического процессора, прежде чем их можно будет использовать для отрисовки кадра. Это означает, что высокое значение этого показателя может означать либо большое количество загрузок небольших ресурсов, либо небольшое количество очень больших ресурсов. Распространенный случай – когда приложение отображает одно растровое изображение, размер которого близок к размеру экрана. Другой случай – когда приложение показывает большое количество значков.

Чтобы уменьшить эту полосу, вы можете использовать такие методы, как:

  • Разрешение растровых изображений не должно значительно превышать размер, в котором они будут показываться. Например, не стоит показывать изображение размером 1024 x 1024 пикселя в формате 48 x 48 пикселей.
  • Использование современных библиотек, таких как Coil, для асинхронной предварительной загрузки растрового изображения перед следующим этапом синхронизации.

Выполнение команд

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

Чтобы система могла отрисовать списки отображения на экране, она отправляет необходимые команды графическому процессору. Обычно это делается с помощью API OpenGL ES.

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

Когда этот сегмент большой

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

for (i in 0 until 1000) {
    canvas.drawPoint()
}

обходится намного дороже, чем:

canvas.drawPoints(thousandPointArray)

Между отправкой команд и отрисовкой списков отображения не всегда есть однозначное соответствие. В отличие от полосы Issue commands (Выполнение команд), которая показывает время, необходимое для отправки команд на отрисовку в графический процессор, показатель Draw (Отрисовка) отражает время, за которое выданные команды были зафиксированы в списке отображения.

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

Поменять буферы местами

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

Когда этот сегмент большой

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

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

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

Другое

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

Когда этот сегмент большой

Если это значение велико, вероятно, в вашем приложении есть обратные вызовы, намерения или другие задачи, которые должны выполняться в другом потоке. Такие инструменты, как Профилировщик ЦП в Android Studio или Perfetto, позволяют отслеживать задачи, выполняемые в основном потоке. Эта информация поможет вам повысить эффективность рекламы. Подробнее о трассировке системы…