Меню Закрыть

Ноушен на Android теперь запускается более чем в два раза быстрее

Последнее изменение: 15.05.2024
Вы здесь:
Расчетное время чтения: 7 мин
Ноушен на Android теперь запускается более чем в два раза быстрее

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

Результат: Сегодня приложение Ноушен для Android запускается более чем в два раза быстрее, чем в начале 2023 года. Вот некоторые из шагов, которые мы предприняли для повышения производительности, и наши постоянные усилия по улучшению мобильного опыта для всех наших пользователей Android.

Переход от веб-кода к нативному коду

Мобильные приложения Ноушен раньше были простыми обертками, которые открывали веб-приложение в WebView. Затем, в 2020 году, мы решили использовать больше нативного кода в ранних продуктах. Перенос видимых поверхностей продуктов с высоким уровнем взаимодействия из веб-версии в нативный код повысил производительность как нативных слоев, так и веб-приложения за счет использования возможностей ОС Android и библиотек ее фреймворка.

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

В 2022 году мы представили родную вкладку «Главная», которая в 3 раза улучшила запуск приложения, а в начале 2023 года — родную вкладку «Поиск», которая улучшила время загрузки более чем на 80 %.

Определение узких мест в производительности

При запуске приложения Ноушен первым экраном, который обычно видят пользователи, является вкладка Home и ее содержимое (например, Favorites, Private и Teamspaces). Этот пользовательский поток, который называется Initial Home Render, является нашим аналогом для измерения производительности при запуске приложения.

Нашей первой задачей было определить, какие части Initial Home Render занимают больше времени, чем ожидалось. Соответствующая метрика, initial_home_render, берется из всех производственных сессий приложения и украшается дополнительными метаданными (например, конфигурацией устройства и состоянием сессии), чтобы помочь в анализе. При проведении анализа мы ориентировались на 95-й процентиль (P95), поскольку именно этот порог отражает опыт подавляющего большинства людей.

Ноушен на Android теперь запускается более чем в два раза быстрее

Внутри initial_home_render мы измерили несколько небольших сегментов/подсегментов, где выполняются тяжелые шаги инициализации и со временем создаются и настраиваются новые зависимости. Например, мы измерили функцию Application onCreate, которая служит точкой входа приложения, чтобы узнать, сколько времени уходит на установку глобальных объектов, таких как компоненты Dagger и сторонние библиотеки. Мы также измерили основную Activity onCreate, где инициализировались объекты и состояние для рендеринга пользовательского интерфейса главной вкладки.

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

В поисках более детальных сведений мы вручную профилировали различные устройства низкого и среднего ценового диапазона, используя комбинацию Android Studio CPU Profiling и трассировки Perfetto в тестовых средах. Затем мы просмотрели эти профили выполнения кода в виде пламенных графиков с помощью таких инструментов, как Android Studio Profiler и Firefox Profile Viewer. Визуализация показывала, на что тратит время процессор устройства во время запуска.

Ноушен на Android теперь запускается более чем в два раза быстрее

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

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

Мы уже определили основных нарушителей — приложение и Activity onCreate — с помощью наших суб-трассировок. Трассировка помогла пролить свет на то, что именно занимает так много времени при выполнении.

Ноушен на Android теперь запускается более чем в два раза быстрее
MainApplication onCreate занимает 165 мс в главном потоке для инициализации зависимостей.

Мы разделили эти узкие места на три категории:

  1. Ожидание инициализации зависимостей
  2. Последовательная загрузка и блокировка операций
  3. Использование основного потока

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

С этими знаниями мы начали вносить постепенные улучшения.

Применение наших знаний на практике

Конфигурация эксперимента по кэшированию

Мы начали с решения проблемы использования библиотеки конфигурации экспериментов в функции onCreate приложения. Эта библиотека, управляющая включением функций и опытов, требовала разрешения сторонних зависимостей и загрузки последней конфигурации эксперимента, прежде чем ее можно было использовать. Поскольку эксперименты часто использовались при запуске (например, вкладка «Главная» во время разработки), мы не могли просто отложить инициализацию.

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

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

Аналитика буферизации и ведение журнала

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

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

Кэширование пользовательской сессии

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

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

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

Одно только это изменение позволило улучшить метрику initial_home_render на ~30 %.

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

Проверка наличия миграций перед инициализацией SQLite

Мобильные приложения Ноушен используют SQLite для хранения данных блока, которые необходимы для загрузки содержимого вкладки «Главная», результатов поиска и рендеринга страниц в веб-приложении.

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

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

Ноушен на Android теперь запускается более чем в два раза быстрее
215 мс на устройстве под управлением Android 13 с 12 ГБ оперативной памяти.

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

Перенос сериализации JSON в рабочий поток

Мы используем Message Ports, IPC-канал, предоставляемый WebView, для связи между веб-приложением и нативным слоем. Сообщения, отправляемые и получаемые через Message Ports, представляют собой JSON-строки разного размера, которые десериализуются и сериализуются нативным уровнем. Трассировка, которую мы зафиксировали во время профилирования, показала, что в некоторых случаях — часто во время запуска приложения — большие JSON-блобы десериализовались в главном потоке при синхронизации общего состояния между слоями. Как и в предыдущих примерах, мы смогли исправить это, внедрив буферы и распараллеливание, что позволило перенести сериализацию/десериализацию исключительно в фоновый поток.

С каждым выпуском запуск приложения немного улучшался. Между версиями мы оценивали влияние каждого изменения и при необходимости вкладывали дополнительные средства. К концу года эти и другие более мелкие изменения привели к ускорению начального рендера P95 на ~45 %.

Ноушен на Android теперь запускается более чем в два раза быстрее

Базовые профили

Большая часть приложения Ноушен, включая вкладку «Home», построена с помощью Jetpack Compose. Мы добавили поддержку базовых профилей, как только они стали доступны, поскольку запуск приложения часто происходил медленнее, чем ожидалось, даже в релизных сборках. Обещание базовых профилей, позволяющих решить проблему инициализации Compose и производительности рендеринга при запуске приложения, было очень интересным.

Мы создали базовые профили для приложения Ноушен Android, определив путь запуска приложения с помощью UIAutomator в тесте JUnit. Этот тест запускал приложение, ждал, пока отобразится вкладка Home, затем прокручивал и разворачивал различные разделы и страницы в тестовом рабочем пространстве. После каждого успешного запуска тестовый прогонщик выдавал сгенерированный файл baseline-prof.txt, который можно было упаковать в сборки релиза.

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

Убедившись в эффективности профилей, мы ввели в действие процесс их генерации. В процессе сборки каждого релиза сначала запускался процесс генерации профилей Baseline Profile на реальных устройствах, работающих в Firebase Device Lab, а затем они включались в создаваемый APK/AAB релиза. По мере того как мы вносили улучшения в запуск приложения и реструктурировали код, старые определения профилей автоматически заменялись новыми.

Измерение улучшений

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

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

Путь вперед

Улучшения, которые мы внесли благодаря профилированию, введению базовых профилей и различным более мелким оптимизациям, дали значительный результат: запуск приложений теперь происходит в два раза быстрее, чем в начале 2023 года. Пользователи также должны заметить значительные улучшения в производительности прокрутки на вкладках «Главная» и «Поиск».

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

Вам не терпится улучшить производительность и качество приложений? Подайте заявку на наши открытые роли здесь.

Была ли эта статья полезной?
Нет 0
Просмотров: 231

Читать далее

Следующий: Создание и масштабирование озера данных Ноушен