Site icon AppTractor

Поиск регрессий в Compose

Программное обеспечение подобно энтропии: ее трудно понять, она ничего не весит и подчиняется второму закону термодинамики, то есть всегда увеличивается, — Норман Огастин.

Отказ от ответственности

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

Вперед!

Введение

Каждый день в кодовые базы добавляется новый код. Новый код в основном означает большее потребление памяти и большее время выполнения. Это в конечном итоге снижает производительность приложения, если код написан неаккуратно. Эти регрессии можно отлавливать двумя способами: либо проактивно (во время обзоров PR), либо реактивно (после того, как приложение попадет к пользователям и они начнут жаловаться).

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

Затем наступает черед реактивного способа. В этом случае у вас, скорее всего, нет такой навороченной системы, как Macrobenchmark. Вы знаете, что новая версия приложения имеет регресс производительности, только потому, что либо ваши показатели Frozen Frame/Slow Frame показывают регресс в Play Store, либо ваши пользователи жалуются, что приложение работает медленно и лагает.

В обоих этих сценариях (проактивном и реактивном), «что» именно вызвало регресс, все еще трудно найти, особенно когда у вас есть сотни, если не тысячи Composable, которые рендерятся каждую секунду.

Эта статья НЕ о том, как определить, есть ли регресс или нет. Скорее, этот блог посвящен тому, как легко найти «что» или «какой» компонент вызвал регрессию.

В этой статье мы создадим приложение, добавим регрессии и сравним версию до и после с помощью Perfetto и Diffetto.

Контекст

Что такое Perfetto?

Perfetto — это веб-инструмент для визуализации и изучения файлов трассировки. Он предлагает сервисы и библиотеки для записи трассеров на уровне системы и приложений. Если вы впервые знакомитесь с Perfetto, я рекомендую посмотреть это видео (необязательно). Это одно из лучших видео, в котором рассказывается об основах Perfetto.

Что такое Diffetto?

Diffetto — это крошечный инструмент, который я написал для сравнения двух трейсов Perfetto. В этой статье мы рассмотрим, как его использовать.

Приложение

Чтобы упростить статью, я собираюсь написать композабл Counter без регрессии (до) и с регрессией (после).

До:

@Composable
private fun MyApp() {
    var count by remember { mutableIntStateOf(0) } // state
    Text("Count is $count", fontSize = 20.sp) // text
    Button(onClick = { count++ }) { // button to update state
        Text("INCREMENT")
    }

    AnotherComposable(count) 
    SomeOtherComposable(count)
}

@Composable
fun AnotherComposable(count: Int) {
    Text("Hello from AnotherComposable -> $count")
    runBlocking { delay(200) } // simulating existing jank (200ms)
}

@Composable
fun SomeOtherComposable(count: Int) {
    Text("Hello from SomeOtherComposable -> $count")
    runBlocking { delay(100) } // simulating existing jank (100ms)
}

После:

@Composable
private fun MyApp() {
    var count by remember { mutableIntStateOf(0) } 
    Text("Count is $count", fontSize = 20.sp) 
    runBlocking { delay(1000) } // introducing new regression of 1 second 🔴
    Button(onClick = { count++ }) { 
        Text("INCREMENT")
    }

    AnotherComposable(count)
    SomeOtherComposable(count)
}

Пожалуйста, помните, что наша цель здесь — не просто найти плохой код, а найти регрессию (новый плохой код).

Трассировка

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

Хотя вышеприведенное руководство охватывает основы трассировки, я хотел бы упомянуть несколько моментов, которые очень важны при трассировке Compose.

  1. Не отслеживайте debug сборку. Создайте новый вариант benchmark, расширяющий конфигурацию вашей релизной сборки, но без обфускации ProGuard. Причина в том, что Jetpack Compose включает множество тяжелых фич в отладочные сборки из инструментов IDE, а также компилятор выполняет множество оптимизаций в релизной сборке. Если вы профилируете debug сборку, то в итоге вы можете исправить проблемы, которые не являются реальными проблемами, с которыми сталкиваются пользователи, и упустить из виду то, что реально происходит. Дополнительную информацию о настройке варианта бенчмарка можно найти здесь.
  2. Существует множество способов создания файлов трассировки. Вы можете использовать IDE, терминал, а большинство последних устройств Android также поддерживают трассировку в устройстве. Вы также можете использовать фреймворк Macrobenchmark, который сгенерирует файлы трассировки за вас. По моему мнению, самый простой способ — использовать IDE, но есть еще более крутой способ, который лично мне нравится больше всего, а именно команда record_android_trace. Эта команда записывает трассеры и открывает их автоматически в perfetto одним щелчком мыши. Это огромная экономия времени.
  3. Не добавляйте зависимость трассировки с помощью implementation(...) Используйте сборочную вариацию вызова implementation. Например, используйте benchmarkImplementation. Это позволит избежать утечки зависимости трассировки в релизную сборку, что приведет к увеличению размера APK.
  4. Старайтесь использовать трассировки с небольшой продолжительностью. Это поможет вам быстрее выявить проблему. Длинные трассировки принесут много шума, что затруднит фокусировку на одной проблеме. Например, если вы знаете, что есть проблемы при загрузке страницы, а также при нажатии на кнопку внутри страницы, не объединяйте эти два взаимодействия в одну трассировку. Разбейте их на два отдельных файла трассировки и проанализируйте.

Шаги трассировки

Я настроил трассировку, следуя приведенным выше инструкциям, и теперь мы можем перейти к генерации файлов трассировки. Для создания файла трассировки я выполнил те же шаги, что и в приложениях «до» (лучшая версия) и «после» (регрессированная версия).

  1. Откройте приложение
  2. Начните трассировку
  3. Нажмите кнопку
  4. Остановить трассировку

Анализ трассировки

На этом этапе у вас должно быть два файла трассировки. Назовем их before.trace и after.trace. Теперь откройте их в Perfetto. Для удобства сравнения я обычно открываю их рядом друг с другом следующим образом.

И, как вы можете видеть, мы добавили регрессию.

Теперь вы можете задаться вопросом: «Зачем нам нужен еще один инструмент для поиска компонента? Он хорошо виден на самом скриншоте». Вы правы! В данном случае это действительно так. Но в реальном приложении могут быть сотни, если не больше, компонентов, которые рендерятся одновременно, и очень сложно получить такую визуализацию и сразу увидеть регрессию.

Diffetto

Diffetto — это крошечное веб-приложение (созданное с помощью Compose Web), которое может помочь определить регрессию по двум файлам трассировки. Вот как его использовать.

В Perfetto при выборе региона вы получите «Pivot Table». Скопируйте данные таблицы «до» и «после» следующим образом:

а затем вставьте их в поля Diffetto «до» и «после» соответственно:

После этого нажмите кнопку «Find Diff», и инструмент сгенерирует таблицу следующего вида:

Как вы уже поняли, Diffetto — это не что иное, как инструмент для обработки текста. Он преобразует предоставленный вами текст в таблицу. Но Diffetto — это не просто конвертер текста в таблицу. Он знает некоторые вещи об этих данных трассировки. По сути, он знает об определенных узлах и о том, что каждый из них означает. Эти знания затем используются для фильтров, которые вы видите в правом верхнем углу. Фильтры используются для выявления виновника и уменьшения шума, который присутствует в файле трассировки. Давайте рассмотрим некоторые фильтры.

Фильтры

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

Что касается самой таблицы, то по умолчанию она отсортирована по столбцу Diff (ms) в порядке убывания. Это означает, что самая верхняя регрессия будет первой строкой. Как видно на скриншоте выше, задержка в 2000 мс, которую мы добавили, хорошо видна в столбце Diff. Также, если вы заметили, для других композитных функций столбец Diff практически равен нулю, что означает отсутствие регрессии. Вы можете управлять упорядочиванием, щелкая по заголовку каждого столбца, и это соответствующим образом перестроит таблицу.

Рекомпозиционная регрессия

С помощью Diffetto вы также можете отлаживать рекомпозиционные регрессии, чтобы найти наиболее перекомпонуемый элемент. Колонки Before count, After count и Count diff (после — до) показывают количество узлов с одинаковыми именами в логе. Это означает, что одна нода = 1 (ре)композиция. Давайте изменим AnotherComposable так, чтобы в нем появились ненужные рекомпозиции, и посмотрим, как это покажет Diffetto.

@Composable
fun AnotherComposable(count: Int) {
    var anotherState by remember { mutableIntStateOf(0) } // adds a new state
    LaunchedEffect(count) {
        anotherState = 0
        delay(2000) // wait for parent runBlocking to settle down
        while (anotherState < 10) {
            anotherState++ // increments it every 100ms until it reach 10; 
            delay(100)
        }
    }

    Text("Hello from AnotherComposable -> $count $anotherState") 
    runBlocking { delay(200) }
}

Теперь таблица Diffetto выглядит следующим образом:

Как видите, дополнительные 10 рекомпозиций, которые мы добавили для AnotherComposable, теперь видны в таблице инструмента.

Заключение

Должен сказать, что хотя Diffetto — это крошечное приложение, его мощь не очень заметна в этом примере.

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

Источник

Exit mobile version