Производительность приложений — неотъемлемая часть пользовательского опыта. Приложение, которое часто зависает или долго запускается, не удовлетворит наших клиентов. Если время ожидания загрузки результатов поиска или экрана с информацией об отеле слишком велико, это может отвлечь от планирования предстоящего отпуска. Мы бы предпочитаем этого избегать. Однако каждая новая функция может несколько снизить производительность приложения, а некоторые изменения могут оказать более значительное влияние, и все это может выйти из-под контроля.
Ключевым аспектом снижения проблем с производительностью в мобильных приложениях является надлежащий мониторинг. Без него любые усилия по улучшению или сохранению производительности превратятся в необдуманную работу.
Краткая история команды App Performance
В Booking.com мы уже давно следим за показателями производительности приложений. Например, первая метрика времени запуска для iOS была введена в 2016 году. Примерно в 2019 году была создана команда, отвечающая за мониторинг и улучшение производительности.
К 2021 году команда поняла, что существующая система мониторинга производительности устарела, ненадежна и не полностью соответствует нашим требованиям, поэтому ее необходимо было пересмотреть.
Занимаясь функциональными улучшениями в метриках, мы также решили полностью переписать библиотеки производительности, одновременно переходя от старых языков Objective-C/Java к современным Swift/Kotlin. На протяжении всего этого процесса мы проектировали наши библиотеки так, чтобы они были полностью независимы от других инфраструктур Booking, вводили внешние зависимости, такие как эксперименты, хранение и работа с сетью.
Почему бы не использовать существующие инструменты сторонних разработчиков
Нет недостатка в бесплатных и платных инструментах для мониторинга производительности приложений. Apple и Google предлагают некоторые готовые решения для мониторинга, есть и несколько крупных сторонних игроков, таких как Firebase Performance.
Однако к нашему инструменту мониторинга было три основных требования, все из которых были связаны с интеграцией в инфраструктуру Booking и учетом особенностей нашей культуры разработки:
- Экспериментирование: Учитывая сильную культуру экспериментов в компании, наиболее важным требованием было обеспечить надежную и простую интеграцию с нашей внутренней инфраструктурой экспериментов.
- Оповещения: Нам нужна гибкая система оповещения/SLA, которая может оперативно уведомлять различные команды о снижении производительности. Метрики должны поступать с реальных устройств как можно быстрее, чтобы обеспечить своевременное оповещение.
- Гибкость: Мы хотели иметь возможность настраивать существующие метрики, определенные Apple и Google. Например, на iOS мы стремимся различать время запуска приложения и время запуска первого экрана. Это означает, что мы заканчиваем измерение времени запуска раньше, чем это делает система мониторинга Apple по умолчанию.
К сожалению, ни одно стороннее решение не соответствовало даже двум из трех наших критериев. Поэтому нам пришлось разрабатывать собственное.
Что мы измеряем
Признав необходимость мониторинга производительности, мы должны были определить, какие показатели отслеживать. Каждая метрика должна отражать болевую точку пользователя. Мы определили две основные проблемы пользователей: время ожидания и плавность интерфейса. Это позволило нам сосредоточиться на трех основных метриках:
- Время запуска приложения
- Время перехода экрана в интерактивное состояние (Screen time to interactive, TTI)
- Производительность рендеринга кадров
Основной задачей нашей компании является предоставление быстрых и надежных услуг поиска и бронирования. Поэтому мы собрали достаточно данных от реальных пользователей, чтобы подтвердить, что значительное ухудшение этих показателей часто влияет на конверсию и почти всегда негативно сказывается на показателях вовлеченности пользователей.
Время запуска приложения
Метрика App Startup Time измеряет время в миллисекундах с момента нажатия пользователем иконки приложения на главном экране до того, как приложение отрисует свой первый кадр.
Обе платформы также различают «холодные» и «теплые» запуски приложений, но мы сосредоточились на улучшении «холодных» запусков, когда система не может воспользоваться состоянием приложения, ранее кэшированным в памяти. Это связано с тем, что «теплые» запуски в основном зависят от производительности конкретных экранов, открывающихся первыми при возвращении пользователя в приложение (и, как вы увидите в следующем разделе, мы измеряем эту метрику отдельно).
Более подробную информацию и официальные рекомендации по процессу запуска приложений для каждой платформы вы можете найти в официальной документации (iOS и Android).
Время до интерактивности
Время до интерактивности (TTI) — это время в миллисекундах, которое проходит с момента начала создания экрана до появления первого кадра содержательного контента. Для этого:
- Должна быть готова вся внутренняя настройка
- Пользовательский интерфейс размещен и отрисован
- Наиболее важные данные получены и показаны пользователю
- Главный поток готов к обработке входящих событий
TTI всегда включает в себя производительность нативного приложения, а для некоторых конкретных экранов время также может зависеть от производительности сети или хранилища. Эта метрика позволяет нам отслеживать, как быстро пользователи могут начать использовать экран по его основному назначению.
Изначально эта метрика была определена Google для веб-разработки, но мы обнаружили, что она очень полезна и отлично подходит и для мобильных приложений.
Чтобы исследовать деградацию TTI, необходимо понять ее причины. Для этого мы используем вспомогательные метрики. Мы отслеживаем время в реальное время выполнения и задержку каждого сетевого запроса, связанного с загрузкой экрана, что помогает нам выявить деградацию, вызванную бэкендом. Для экранов, которые связаны с большими объемами операций чтения/записи, также имеет смысл отдельно отслеживать производительность хранилища.
Кроме того, мы используем метрику Time To First Render для выявления деградации, вызванной созданием и рендерингом экрана (см. следующий раздел).
Время до первого рендера
Время до первого рендера (Time To First Render, TTFR) — это время в миллисекундах, которое проходит с момента начала создания экрана до момента рендеринга первого кадра.
Оно начинается в то же время, что и общее измерение TTI, но может остановиться раньше. В наиболее распространенном случае экран должен быть готов к отрисовке как можно скорее, но не обязательно сразу показывать значимое содержимое. Обычно экран может показывать некий «индикатор прогресса» и выполнять некоторые тяжелые инициализации в фоновом режиме. Мы прекращаем отслеживание TTFR, как только отрисовывается первый кадр, поэтому метрика довольно близка к измерению времени создания экрана. Это позволяет нам предотвратить зависание потока пользовательского интерфейса во время создания, что приводит к улучшению пользовательского опыта.
Эта метрика напрямую влияет на TTI и может также влиять на производительность рендеринга.
Производительность рендеринга
Чтобы взаимодействие пользователя с приложением было плавным, приложение должно рендерить кадры менее чем за 16 мс, чтобы достичь 60 кадров в секунду (примечание: на многих современных устройствах этот показатель может быть уменьшен из-за более высокой частоты кадров на экране, 90 или 120 кадров в секунду, но в этой статье мы будем говорить о 60 кадрах в секунду). Если время рендеринга кадра превышает 16 мс, то система вынуждена пропускать кадры, и пользователь будет ощущать это в приложении.
Давайте попробуем представить, как приложение рендерит кадры на временной шкале:
Есть 2 основных фактора, которые влияют на то, насколько плохой может быть производительность рендеринга:
- Длительность фриза: Определяет, сколько времени требуется для рендеринга одного медленного кадра. Чем дольше, тем заметнее проблема с производительностью (например, кадр, который рендерится 500 мс, выглядит гораздо хуже, чем кадр, который рендерится 32 мс).
- Количество или частота фризов: Определяет, как много или как часто пользователи сталкиваются с медленными кадрами. Чем чаще пользователи сталкиваются с одним и тем же медленным кадром с одинаковой длительностью зависания, тем хуже он выглядит.
Учитывая эти два фактора, мы можем вывести определение для метрики, которая довольно точно отражает производительность рендеринга:
- Время замирания (Freeze Time): общее время замирания пользовательского интерфейса из-за отрисовки медленных кадров за одну экранную сессию (или сессию приложения).
На иллюстрации выше мы видим 6 кадров: 3 кадра хороших и 3 — с фризами. Это означает, что 3 хороших кадра были отрендерены в течение 16 мс, а 3 других кадра имеют паузы разной длительности. Мы вычисляем длительность паузы как разницу между фактической длительностью кадра и 16 мс целевой длительностью. Чтобы вычислить общее время фриза, нужно просуммировать длительность всех замираний, происходящих на экране.
Время замирания может быть одинаковым при различном поведении: 1 замораживание с 1000 мс, 100 замораживаний с 10 мс. Кроме того, время замирания может увеличиться без каких-либо дополнительных изменений, просто за счет увеличения продолжительности сессии (например, когда каждый элемент прокручиваемого списка генерирует несколько медленных кадров и пользователь начинает прокручивать его больше, это приводит к увеличению общего времени замирания).
Для выявления таких ситуаций мы также используем две дополнительные метрики:
- Freeze Count: Общее количество медленных кадров (медленнее 16 мс) во время экранной сессии. Чтобы увидеть, изменяется ли паттерн зависаний.
- Продолжительность сеанса: Продолжительность экранной сессии. Чтобы проверить, не изменилась ли продолжительность сессии, что может привести к изменению общего времени фризов.
Обоснование выбора метрики “Время замирания”
И Google, и Apple предлагают метрики для оценки производительности рендеринга. Изначально для мониторинга производительности рендеринга мы использовали метод, реализованный в Firebase, который предусматривал отслеживание медленных кадров (>16 мс для рендеринга) и замороженных кадров (>700 мс). Однако мы обнаружили, что эти метрики неадекватно отражают деградацию производительности рендеринга.
Например, рассмотрим сценарий, в котором представления в списке и так работают медленно, требуя 20 мс для рендеринга. Если время рендеринга увеличится до 300 мс, метрики по-прежнему будут сообщать об одном медленном кадре на представление без каких-либо замороженных кадров, не указывая на значительное ухудшение времени рендеринга.
Более того, существует несоответствие в отражении изменений производительности. Время рендеринга представления, увеличившееся с 15 до 20 мс, регистрируется как такое же изменение метрики, как и увеличение с 15 до 300 мс, что не совсем точно отражает серьезность замедления.
Метрика Apple «Частота зависаний», которая рассчитывается как секунды времени зависания в час, оказалась более соответствующей тому, что нам было нужно. Она похожа на нашу метрику Freeze Time, но нормализуется путем деления общего времени зависания на продолжительность сеанса. Однако такая нормализация привела к тому, что метрика стала слишком чувствительной к изменениям в поведении пользователей.
Например, если функция продукта заставляет пользователей тратить больше времени на прокрутку медленного списка, показатель зависания может показать улучшение, поскольку продолжительность сеанса увеличилась, хотя пользовательский опыт ухудшился из-за большего количества зависаний.
После того как мы столкнулись с различными сценариями, в которых относительная метрика не давала четкого представления о производительности, мы решили использовать абсолютную метрику. Это позволяет нам более точно измерить производительность рендеринга не только для всего приложения, но и для каждой экранной сессии, при этом результаты не будут искажены поведением пользователя или длительностью сессии.
У абсолютной метрики тоже есть определенные ограничения. Например, можно взять тот же пример: если в результате внедрения новой функции продукта пользователи будут чаще прокручивать медленный список, метрика рендеринга ухудшится, даже если технического снижения производительности не произошло. Однако включение дополнительной метрики “Продолжительность сессии” позволяет нам эффективно управлять такими ситуациями.
Основная идея заключается в том, что мы рассматриваем любое увеличение Freeze Time как негативное изменение производительности, независимо от причины (хотя в идеале пользователь вообще не должен видеть никаких зависаний). Конечно, важно реагировать на новые проблемы с производительностью, вызванные появлением новой функции, но также важно обнаружить, что старый экран производит больше фризов, поскольку пользователи начинают взаимодействовать с ним более активно.
Покажите мне код!
Подводя итог всем теоретическим знаниям и четким определениям метрик, мы, наконец, можем реализовать рабочее решение для сбора этих показателей. Недавно мы выложили в открытый доступ наши библиотеки отслеживания производительности для обеих платформ, которые вы можете найти на GitHub:
Эти легковесные библиотеки помогают собирать вышеупомянутые показатели и отправлять их в любую систему аналитики. Мы активно работаем над дальнейшими улучшениями и новыми функциями (например, мониторингом закрытий), однако она уже используется в основном приложении Booking на обеих платформах. Библиотека для iOS также используется в нашем приложении Pulse для владельцев недвижимости и в приложении Agoda от нашей родственной компании.
Не стесняйтесь пробовать, оставлять отзывы, а еще лучше — вносите свой вклад!
Авторы: Вадим Чеповский и Глеб Тарасов