Перформанс 2ГИС для Android

3399fa1329728fddb260fd597a2f1c69.jpg

Каждое большое приложение однажды сталкивается с задачей — увеличить скорость запуска. Не обошла она и приложение 2ГИС на Android. Расскажу, как команда тестирования искала причины медленного запуска.

Первые проблемы производительности 

В ноябре 2021-го на тестовом Huawei Mate 10 приложение запускалось за две секунды, а потом ещё 10 секунд рисовалась карта. То есть пользователю приходилось ждать 12 секунд, чтобы начать использовать 2ГИС — это катастрофически долго.

Проблема оказалась во включённом слое «Метро»: при запуске слоя приложение накладывает дополнительные стили, немного перерисовывая карту. То есть пользователь ждал, пока мы сначала отрендерим карту с одними стилями, а потом их перересуем.

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

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

Ручная проверка

Первые проверки тестовых сборок были ручные. Реально ручные: тестировщик с секундомером в руках запускал приложение пять раз с временным интервалом в 10 минут. Затем снимал «показания» и высчитывал среднее арифметическое. То же самое делали и для предыдущего релиза, чтобы сравнить скорость запуска.

Естественно, это так себе решение — занимает много времени и крайне ненадёжное. 

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

Кроме этого, изменили алгоритм подсчёта:

  1. Отказались от выжидания по 10 минут между запусками приложения, так как девайс за это время может начать «отдыхать» и запустит сберегайзеры.

  2. Количество запусков увеличили до 10 (данных от пяти запусков было мало для анализа).

  3. Стали считать медиану, так как среднее арифметическое плохо работает с аномальными выбросами — подозрительно быстрым или подозрительно медленным запуском приложения.

Такие результаты замеров на тестовых девайсах плюс‑минус совпадали с информацией о скорости запуска от пользователей. Но со временем этот процесс перестал работать.

Критически долгий старт

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

Полностью переработали главный экран

Теперь он выглядит вот так.

2dea9de95fb68c871dcdb2a9c0676277.png

Переделали выдачу

В каждой рубрике фильтры стали настраиваться индивидуально: для еды — виды кухонь и средний чек, для красоты — тип услуг или стоимость стрижки. Ещё появился блок «тут были друзья».

Изменили дизайн карточек компаний и зданий

591fc51ef795b8524488ab621a3e1fca.png

А в апреле 2022 года мы зарелизили «гибрид» — возможность работать с приложением без скачивания города.

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

Автотесты

Мы сформулировали несколько гипотез, из‑за чего могут быть такие «тормоза». Но прежде чем проверять их, решили сформулировать: как достоверно понять, что мы действительно улучшили производительность?

Можно установить сборку с оптимизацией на устройство или эмулятор и запустить некоторое количество раз. Параллельно искать в логах инфу о скорости запуска и её изменениях. В целом неплохой вариант, но есть большой минус — это трудоёмко. Процесс может занимать от 15 до 30 минут, в зависимости от QA / устройства / способа забора метрик.

Кроме этого:

  1. Слишком маленький объём получаемых данных. Теоретически можно и тысячу раз запустить приложение руками, а потом смотреть на показатели скорости запуска и анализировать их. Но на это уйдёт колоссальный объём времени.

  2. Фоновые процессы устройства. В моменте сложно учесть все процессы смартфона, которые могут влиять на производительность.

  3. Троттлинг. Запуск приложения — ресурсозатратная операция для устройства. Девайс совершает кучу тяжёлой работы, увеличивается потребление энергии, из-за чего возможен перегрев. Чтобы хоть как‑то это контролировать, умный телефон начнёт искусственно занижать свою работоспособность.

Мы решили: честнее всего будет сделать так, чтобы этот тестовый стенд (бенчмарк) мог выполняться на реальных устройствах. А ещё лучше — на тех устройствах, которые находятся в топе у наших пользователей.

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

Как мы поступили:

  1. Написали автотест, который запускает приложение N раз с таймаутом в M секунд. Таймаут помогает «не загонять» устройство, при этом не даст ему сильно «уснуть».

  2. Для каждого устройства подобрали оптимальный таймаут, основываясь на относительном стандартном отклонении. Грубо говоря, это процент того, насколько сильно результаты отличаются друг от друга внутри выборки. Чем процент ниже, тем меньше отличий внутри выборки. Например, при таймауте в 10 секунд мы видим, что CV = 40%. Следовательно, можно предположить, что устройству тяжело выполнять работу по запуску каждые 10 секунд, поэтому таймаут увеличиваем, а после — снова смотрим, как изменилось CV. Такую калибровку прошёл каждый девайс, на котором мы будем запускать этот автотест.

PROFIT! В итоге у нас получился автотест, который может запускать параллельно две разные версии приложения. Таким образом боремся с тем, что устройство «живёт своей жизнью». А мы можем сравнивать изменения в скорости запуска, например, между прошлой и текущей версией приложения.

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

Расставляем трейсы

Первым делом исследовали, как анализировать текущие проблемы с перформансом.

Для этого мы начали активно профилировать приложение и расставлять трейсы. Наше приложение включает себя кусочки «разного вкуса»: Qt, QML, C++, Java. Где‑то трейсы уже были расставлены, но их оказалось недостаточно. Где‑то добавили ещё и следили, что происходит в этих кусочках в базовых сценариях: запуск приложения, поиск, открытие карточки. Результаты смотрели в Perfetto.

Выявили узкие места:

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

  2. Множество вещей создавались на старте приложения, хотя для старта были не нужны. Например, элементы измерения расстояния по двум одновременным длинным тапам.
    Как решили бороться: …отложили их создание.

77c0cf7b31fc542a97b0162b19ae97e1.png

  1. Выявили элементы, которые не сразу нужны пользователю при открытии карточки или поиске. Например, экшн‑бар, в котором отображается маршрут, «Позвонить», «Избранное», «Показать вход», шаринг, такси и рекламный CTA.
    Как решили бороться: аналогично — на миллисекунды отодвинули момент создания этих элементов. Это позволило быстро отобразить шапку, тело и, в конечном итоге, сам экшн‑бар.

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

Проблема накопленных данных

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

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

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

Сомнений не осталось — проблема точно в данных.

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

Отказавшись всего от одного метода Directories::logDiskUsage(options);, которым собирали статистику о файлах приложения и считали их размер, мы сильно прибавили в скорости. Всего одна строчка кода — и сотня тысяч довольных пользователей.

Ускоряем открытие карточки и выдачи

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

В карточке оптимизировали открытие двумя простыми способами:  

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

  2. При открытии карточки сначала отображали шапку, затем кард-экшены и только потом — тело карточки, где лежит самая важная для пользователя инфа. При этом некоторые элементы — например, кард‑экшены — отнимали время на визуальное отображение основной информации карточки: название организации, адрес, контактные данные.

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

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

Добавили автотесты

Чтобы контролировать регрессию, добавили автотесты.

1. Бенчмарк скорости открытия карточек с рекламой и местечек.Он делает N открытий карточек подряд: запуск приложения → открыть карточку → снять метрики → снова открыть карточку → …

Кроме этого, он делает N запусков приложения с последующим открытием карточки: запуск приложения → открыть карточку → снять метрики → закрыть приложение → открыть приложение → … 

Это делаем из-за того, что первое открытие карточки после старта приложения — тяжёлая операция.

2. Бенчмарк скорости отрисовки элементов поисковой выдачи.Также измеряем открытия карточек с рекламой. Этот бенчмарк, как и предыдущий, умеет делать N запусков приложений с последующим поиском метрик. А ещё умеет внутри одной сессии делать N замеров.

Итог

Лучше слов всё скажут скринкасты, которые мы записали на трёх устройствах разных классов: Samsung Galaxy A51, Google Pixel (первый) и Redmi Note 8 Pro.

На каждом мы запустили три разных версии 2ГИС — 6.12 (назовём её медленной), 6.15 (это самая свежая, на ней ситуация уже сильно лучше) и ещё одной, которая сейчас в разработке (условно назвали её 6.15+). В каждой версии мы выполнили простое действие — открыли карточку компании.

Сейчас на бою версия 6.20, а мы продолжаем оптимизировать. 

© Habrahabr.ru