Программирование
Как 5 приложений для iOS могут сократить время запуска в среднем на 28%
Миллисекунды имеют значение
Время запуска — важная метрика приложения, которую следует постоянно отслеживать и улучшать. A/B-тесты в ведущих компаниях, занимающихся мобильными приложениями, постоянно показывают, что добавление всего лишь доли секунды может значительно повредить основным показателям использования, таким как количество активных пользователей в день и время, затрачиваемое на приложение на пользователя в день.
Lyft сообщил об увеличении количества пользовательских сеансов на 5% благодаря сокращению времени запуска их приложения для водителей на 21%. Apple сделала время запуска предметом многочисленных презентаций WWDC (1, 2, 3).
Вместо высокоуровневых заявлений о том, что меньшее время запуска лучше большего и о том, что надо избегать распространенных анти-шаблонов, в этой статье будет использоваться конкретный продукт для анализа производительности — Performance Analysis от Emerge — для диагностики конкретных проблем запуска конкретных приложений и предложения улучшений. Этот пост будет посвящен приложениям для iOS, однако инструменты Emerge полностью идентичны как для Android, так и для iOS.
Это настоящие оптимизации, которые применяются к общедоступным сборкам App Store без участия разработчиков.
Как мы измеряли время запуска
Поскольку проанализированные здесь приложения взяты из App Store без какой-либо отладочной информации или исходного кода, мы решили просто определить «время запуска» как самую раннюю точку от запуска приложения до конца applicationDidBecomeActive(_:).
Для команд мобильных приложений, работающих с Emerge, определения времени запуска полностью настраиваются разработчиком. Распространенным вариантом использования будет определение конечных точек после applicationDidBecomeActive(_:) для измерения времени, до которого пользователь может осмысленно взаимодействовать с приложением. Это означает, что измерения времени запуска, показанные ниже, вероятно, являются консервативными оценками по сравнению с тем, что разработчик в компании может считать запуском.
Из-за отсутствия каких-либо символов отладки мы не можем полностью расшифровать многие имена функций. Приложения, которые являются частью интеграции Emerge CI с включенными символами отладки, дадут гораздо более подробные и понятные диаграммы.
Emerge использует современную ферму физических устройств, чтобы обеспечить максимально точные измерения производительности. Следующие измерения основных потоков были проведены на iPhone SE (2020 г.) под управлением iOS 15.4.1.
Важно отметить, что единственное измерение времени запуска приложения — это именно то, что нужно, и его не обязательно экстраполировать для представления большего размера выборки. Контроль дисперсии сродни приручению дикого зверя — все, от приложений, в которые вы вошли и недавно запускали, до типа или даже температуры устройства, может сильно повлиять на результаты.
United Airlines
Приложение United уже подробно обсуждалось другими из-за проблем с размером приложения, но время его запуска также имеет много возможностей для улучшения. Сразу бросаются в глаза три вещи:
- Автоматический инсайт: United тратит 48 мс на JSONDecoder.decode(), который должен выполняться в фоновом режиме или ускоряться с помощью более быстрой сторонней библиотеки JSON, такой как моя собственная ZippyJSON.
- United тратит 677 мс в -[NSPersistentContainer loadPersistentStoresWithCompletionHandler:]. Подобная работа с основными данными должна выполняться вне основного потока. Такая огромная задержка, вероятно, указывает на наличие других антипаттернов.
- United тратит 103 мс на LPMessagingSDK, который вызывает Bundle.init(identifier:). Этот метод принимает идентификатор и возвращает пакет, соответствующий этому идентификатору (CFBundleIdentifier в Info.plist). Хотя этот метод может показаться безобидным, он должен загружать все пакеты с диска, пока не найдет пакет с правильным идентификатором. Сюда входят как пользовательские пакеты, такие как фреймворки, так и все многочисленные пакеты Apple. Когда пакеты загружаются впервые, они в некоторой степени кэшируются. При повторном запуске приложения сразу же после этого время запуска сокращается на 20 мс, но для холодного запуска эта стоимость загрузки должна быть потрачена. Приложение следует изменить, чтобы отложить инициализацию LPMessagingSDK до тех пор, пока эта функция действительно не понадобится (что, вероятно, только тогда, когда пользователь переходит на страницу поддержки).
Chipotle
- Автоматический инсайт: приложение United не единственное, кто стал жертвой проблем с LPMessagingSDK. Приложение Chipotle тратит 187 мс на запуск в LPMessagingSDK в Bundle.init(identifier:), который, как обсуждалось выше, может быть перемещен из процедуры запуска.
Curb
Curb — единственное приложение, которое мы анализируем здесь и которое использует Salesforce Service Cloud SDK. Этот SDK предоставляет API и пользовательские интерфейсы для инструментов управления взаимоотношениями с клиентами, таких как чат поддержки. Как и в случае с LPMessagingSDK, Salesforce Service Cloud SDK выполняет очень дорогостоящую загрузку пакетов. В частности, он тратит 83 мс на Bundle.allFrameworks, а затем еще 93 мс на NSArray.filtered(using:) сразу после этого. Эти вызовы методов находятся в самом начале запуска (или, по крайней мере, в той части, которую мы можем записать), что позволяет предположить, что они являются инициализаторами. Инициализаторы — это специальные функции, которые неявно запускаются во время начала запуска, например, во время методов NSObject.load().
Если мы переключим «Свернуть системные вызовы» в пользовательском интерфейсе, то увидим, что они действительно являются инициализаторами, которые запускаются не кодом приложения, а вместо этого dyld4::Loader::findAndRunAllInitializers. После декомпиляции этой библиотеки с помощью Hopper мы обнаружим, что последующая функция вызывает Bundle.allFrameworks и делает по существу следующее:
var frameworksList: [Bundle]? ... func initializeFrameworkBundles() { ... let allFrameworks = NSBundle.allFrameworks let predicate = Predicate(format:"bundleIdentifier BEGINSWITH %@", "com.salesforce") frameworksList = allFrameworks.filteredArray(predicate:predicate) ... }
NSBundle.allFrameworks стоит дорого, поскольку выполняет некоторую первоначальную настройку и/или выборку из кеша для каждого отдельного фреймворка. Это включает в себя как предоставленные пользователями фреймворки, так и системные Apple. Вызов NSArray.filteredArrayUsingPredicate(using:) дорог по той же причине: он вызывает NSBundle.bundleIdentifier для каждого отдельного пакета фреймворка. Это означает, что помимо первоначальной настройки, выполненной в NSBundle.allFrameworks, теперь необходимо прочитать Info.plist для каждой платформы и получить значение CFBundleIdentifier. Хотя это может ускориться при дальнейших запусках из-за заполнения кеша, это все равно займет нетривиальное количество времени, и холодный запуск остается критическим случаем.
В Salesforce могли бы избежать всего этого, сократив поиск только до пользовательских фреймворков в *bundle path*/Frameworks, или, что еще лучше, поиска фреймворков по имени, которое, как известно, может быть предоставлено Salesforce. Для разработчика приложений, который использует Salesforce, это сложнее обойти. В отличие от LPMessagingSDK, где разработчик может контролировать, когда происходит инициализация, и полностью удалить ее из пути запуска, у разработчика нет такой опции для функции инициализации, которая автоматически запускается системой.
Разработчикам SDK: пожалуйста, не используйте функции инициализации. Их влияние на запуск гораздо сложнее измерить, как и предотвратить/отложить в запуске. Они могут повредить не только производительности, но и стабильности, как я думаю, мы все помним фиаско Facebook SDK.
Имея это в виду, вот несколько подходов к смягчению последствий:
- Для любителей приключений — попробуйте не связывать фреймворки Service Cloud, вместо этого включите их в пакет приложений и используйте Bundle.load() для их загрузки перед вызовом API (поскольку часто SDK требуется только для очень специфических, редко используемых экранов, например, поддержка клиентов).
- Используйте Service Cloud REST API напрямую, а не через их библиотеки.
- Полностью используйте альтернативный сервис.
Помимо проблем, упомянутых выше, Salesforce Service Cloud SDK тратит 67 мс на выполнение class_conformsToProtocol и objc_copyClassList (возможно, перебирая все классы, чтобы определить, какие из них соответствуют какому-либо протоколу) в настройке без инициализатора. Всю эту настройку, скорее всего, можно вынести из загрузки приложения.
Что касается других SDK, мы видим, что NewRelic занимает 4% при запуске из-за Method Swizzling, LeanPlum — 3% из-за Method Swizzling, а Realm — 1% для objc_copyClassList (вероятно, только 1%, потому что Service Cloud SDK разогревает кэши, вызывая это функция в первую очередь). Хотя Realm может быть неотъемлемой частью запуска, кажется, что два других SDK могут, по крайней мере, немного задерживаться, чтобы позволить отображать экран запуска, прежде чем блокировать основной поток.
Walmart
- Автоматический инсайт: Walmart потратил 20 мс на print, который не следует использовать в рабочем приложении App Store.
- Walmart также потратил 197 мс на запуск String.init(describing:). Как правило, это признак того, что приложение пытается либо использовать эту строку в качестве уникального идентификатора (в этом случае вместо этого следует использовать ObjectIdentifier), либо что оно может вызывать этот метод как часть ведения логов, который можно просто удалить.
Zoom
- И, наконец, мой личный фаворит. Приложение Zoom фактически тратит 41 мс в спящем режиме в основном потоке во время запуска.
Вывод
Даже в самом крупном масштабе управление производительностью мобильного приложения является чрезвычайно сложной задачей. Время запуска — одна из наиболее распространенных и доступных метрик производительности, но достижение точных измерений в процессе разработки — серьезное препятствие.
Используя продукт Emerge Performance Analysis, мы смогли проанализировать и предложить улучшения времени запуска для пяти популярных iOS-приложений, загруженных непосредственно из App Store. Многие идеи, предлагаемые в этом сообщении в блоге, являются автоматическими выводами продукта Emerge Performance Analysis, который автоматически определяет и предлагает улучшения производительности.
После интеграции в CI разработчик может вносить изменения и сразу же видеть, снизила ли эта функция производительность приложения или улучшила ее, непосредственно в пул-реквесте.
Удачи всем, кто работает над улучшением времени запуска и производительности приложений, и, пожалуйста, дайте нам знать, если у вас есть какие-либо вопросы!
-
Видео и подкасты для разработчиков1 месяц назад
Lua – идеальный встраиваемый язык
-
Новости1 месяц назад
Poolside, занимающийся ИИ-программированием, привлек $500 млн
-
Новости1 месяц назад
Видео и подкасты о мобильной разработке 2024.40
-
Новости1 месяц назад
Видео и подкасты о мобильной разработке 2024.41