Connect with us

Разработка

Как Apple в iOS 27 удалось сократить время запуска на 30%

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

Опубликовано

/

     
     

Время запуска — самый важный показатель, который вы никогда не оптимизируете. Представьте, что вы — WhatsApp. У вас невероятное количество ежедневно активных пользователей — кроме США, по какой-то причине. Каждый из них может выполнять тёплый запуск приложения по десять раз в день. А может, и чаще.

Если запуск занимает 0,5 секунды, умножить это на 10, а затем ещё на невероятное количество пользователей, то даже улучшение на 1%, вероятно, сэкономит несколько человеческих жизней. Я не проверял расчёты и не собираюсь.

К счастью, Apple свои расчёты проверила. На сессии WWDC 2019 года «Optimizing App Launch» компания подсчитала, что при миллиардах ежедневных запусков сокращение времени запуска всего на одну миллисекунду ежедневно экономит примерно столько времени, сколько нужно, чтобы отправить ракету на Марс.

В этом году на WWDC Apple устроила своего рода «год Snow Leopard». Меньше эффектных функций, меньше ненужных переделок интерфейса и больше незаметных системных оптимизаций. Компания заявила, что в iOS 27 скорость запуска приложений выросла на 30%.

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

Мы:

  1. разберём основные вещи, которые разработчику нужно знать о времени запуска;
  2. исследуем улучшения iOS 26 и iOS 27 с помощью Instruments;
  3. выясним, что именно Apple действительно оптимизировала.

Теория запуска

Виды запуска

Существует три вида запуска приложения.

🐢 Холодный запуск

Холодный запуск — это то, что обычно подразумевается под словом «запуск». Процесс приложения ещё не существует, а исполняемый файл находится в постоянном хранилище. На WWDC 2016 объясняли, что настоящий холодный запуск в основном происходит только при первом запуске после установки приложения или перезагрузки устройства. Однако именно он создаёт первое впечатление, поэтому о его скорости стоит беспокоиться.

🐇 Тёплый запуск

При тёплом запуске iOS создаёт новый процесс, однако страницы памяти с исполняемым кодом уже активно кэшируются ядром в файловом кэше. Кроме того, dyld сохраняет заранее рассчитанный план запуска. Проще говоря, необходимые данные уже находятся ближе к месту, где они понадобятся.

🐅 Горячего запуска не существует

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

Этапы запуска

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

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

Address Space Layout Randomization, или ASLR, является важным механизмом защиты от эксплуатации ошибок памяти. При каждом запуске он случайным образом меняет адреса многих фреймворков в памяти процесса. Поэтому dyld выполняет так называемые fixups — заменяет ссылки на символы реальными адресами памяти.

После этого среда выполнения Swift настраивает метаданные типов, соответствия протоколам, обобщённые типы. Наконец, может выполниться часть вашего собственного кода: статические инициализаторы и методы Objective-C load().

Это огромный объём работы, и вскоре мы увидим все эти процессы в трассировках Instruments.

За знание этапов после main работу вам, скорее всего, не дадут. Если вы увлекаетесь приложениями командной строки или просто любите полный контроль, у вас может быть собственная явная функция main():

// main.swift

import UIKit

UIApplicationMain(
    CommandLine.argc,
    CommandLine.unsafeArgv,
    nil,
    NSStringFromClass(AppDelegate.self)
)

UIApplicationMain создаёт объект приложения, настраивает цикл запуска, делегат приложения и в итоге вызывает application(_:willFinishLaunchingWithOptions:). Остальную последовательность вы, вероятно, знаете. А если нет, на моём сайте есть много материалов про разработку под iOS.

Запуск заканчивается, когда отображается первый кадр интерфейса. Оптимизация этой части — обычная инженерная работа: профилирование, выполнение задач параллельно, минимизация объёма работы на критическом пути до момента, когда пользователь увидит нужный ему интерфейс.

Чтение трассировки запуска

Инструмент App Launch предоставляет удобное визуальное представление описанной выше теории.

Как Apple в iOS 27 удалось сократить время запуска на 30%

  • Этап Launch Executable охватывает весь процесс до вызова main.
  • Validating Closure в начале трассировки указывает на тёплый запуск. На этом этапе загружается план запуска, заранее рассчитанный dyld. При холодном запуске вместо него отображается Building Closure. Этот этап находится ближе к концу и занимает значительно больше времени.
  • Map Image получает реальные пути к файлам динамических фреймворков и отображает их в память процесса через mmap. Именно здесь чрезмерное количество динамических фреймворков может замедлить запуск.
  • Apply Fixups связывает ссылки на символы с реальными адресами памяти.
  • ObjC Image Init делает классы доступными среде выполнения Objective-C.
  • Run Static Initializer запускает статические инициализаторы.
  • dlopen отвечает за загрузку библиотек во время выполнения. Вы даже можете вызывать эту функцию самостоятельно.

После main в трассировке также можно увидеть инициализацию UIKit, вызов didFinishLaunchingWithOptions, создание UIScene, итоговый переход в состояние Foreground — Active.

Как Apple в iOS 27 удалось сократить время запуска на 30%

В Xcode 27 появился удобный flame graph.

В инструменте Time Profiler нажмите маленькую синюю кнопку в правом верхнем углу, выберите процесс приложения, затем главный поток. После этого появится график выполнения.

Как Apple в iOS 27 удалось сократить время запуска на 30%

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

Flame graph полезен не только для анализа запуска, но и для других задач оптимизации производительности.

Как Apple в iOS 27 удалось сократить время запуска на 30%

У меня есть только один шанс 🎯

Я не Apple. У меня нет склада дешёвых iPhone 11 Pro Max, на которых можно проводить профилирование. У меня есть только одна возможность в год выполнить полноценное сравнение производительности до и после обновления операционной системы.

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

К счастью, я вспомнил об эксперименте прямо перед тем, как автоматически установилась бета-версия системы. Едва успел.

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

Как Apple в iOS 27 удалось сократить время запуска на 30%

Хорошо. Вот часть, которую вы ждали. Посмотрим, что изменилось в iOS 27.

iOS 26 против iOS 27

Поскольку я очень глупый, на iOS 26 я сделал только три трассировки — два тёплых запуска и один холодный. После этого на iOS 27 я уже сделал два тёплых запуска и два холодных.

Я не настоящий учёный, а вы в основном читаете это ради хорошего настроения. При необходимости можете провести профилирование самостоятельно.

Код и трассировки эксперимента опубликованы в открытом доступе.

Холодный запуск в iOS 26

Общее время: 1090 мс

Из них:

  • pre-main — около 550 мс;
  • post-main — около 540 мс.

Как Apple в iOS 27 удалось сократить время запуска на 30%

Тёплый запуск в iOS 26

Общее время: 582 мс

Из них:

  • pre-main — около 145 мс;
  • post-main — около 437 мс.

Как Apple в iOS 27 удалось сократить время запуска на 30%

Сразу заметно, насколько сильно сократилось время pre-main. Этот этап стал почти в четыре раза быстрее. Особенно значительно ускорились загрузка замыканий и отображение памяти. Синий этап fixups занимает примерно столько же времени. Это логично, поскольку ASLR применяется при каждом запуске.

Хорошо. Давайте пойдем дальше.

Необратимо устанавливает iOS 27.

Холодный запуск в iOS 27

Общее время: 862 мс

Из них:

  • pre-main — около 470 мс;
  • post-main — около 390 мс.

Как Apple в iOS 27 удалось сократить время запуска на 30%

Тёплый запуск в iOS 27

Общее время: 449 мс

Из них:

  • pre-main — около 67 мс;
  • post-main — около 382 мс.

Как Apple в iOS 27 удалось сократить время запуска на 30%

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

  • Холодный запуск стал быстрее на 21%.
  • Тёплый запуск стал быстрее на 23%.
Как Apple в iOS 27 удалось сократить время запуска на 30%

Когда то, что вы пытаетесь проверить, на самом деле близко к заявленным Apple 30%.

Большая часть улучшения пришлась на фазу pre-main. Измерения показали — перестроение closure ускорилось примерно в 1,5 раза, fixups — в 2,5 раза, статические инициализаторы — в 3 раза. Оставшаяся часть pre-main в основном состоит из операций ввода-вывода, например отображения в память динамических исполняемых файлов. Эти этапы на обеих версиях системы выглядят примерно одинаково.

Форма фазы dyld также осталась практически неизменной. Это не полная переработка dyld, которую Apple выполняла в предыдущих версиях системы. Однако результаты согласуются с заявлениями компании. Apple утверждает, что ускорение на 30% обеспечили две технологии:

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

Этап post-main также немного ускорил отображение первого кадра, когда Core Animation отправляет интерфейс на отрисовку. Большинство остальных этапов запуска в моём тестовом проекте представляют собой глупые вызовы sleep(), которыми я имитировал плохо спроектированную последовательность запуска.

Как Apple улучшала запуск приложений

За последние десять с лишним лет Apple неоднократно рассказывала об ускорении запуска.

2017 год: dyld3

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

Разбор Mach-O и разрешение символов были вынесены в отдельный процесс, а результаты кэшировались в виде launch closures.

2018 год: запуск до двух раз быстрее

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

2019 год: ещё одно ускорение до двух раз

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

2022 год: некоторые приложения запускаются почти вдвое быстрее

Ускорение обеспечили улучшения динамического компоновщика. Среди них — заранее рассчитанные соответствия протоколам, page-in linking, отложенное выполнение fixups при обращении к странице памяти вместо выполнения всех операций во время запуска.

2026 год: запуск до 30% быстрее

Apple объясняет ускорение оптимизированным планировщиком процессора и предварительной загрузкой ключевых данных приложения. Я не буду снова это разбирать. Также я нашёл интересный материал CodeColorist с анализом декомпилированного кода, в котором рассматриваются возможные оптимизации кэша dyld.

Хм.

Ускорение в два раза, затем ещё в два, ещё в два и потом в 1,3 раза. В сумме получается потенциальное ускорение запуска некоторых приложений примерно в 10,4 раза.

Но подождите.

С 2016 года мы также перешли от процессоров A9 к A19. Это ещё примерно десятикратный прирост. Тогда почему запуск приложений не стал примерно в сто раз быстрее, чем в 2016 году?

Думаю, нам просто нужно выполнять меньше всякой ерунды во время запуска.

Давайте отключим хотя бы часть этих маркетинговых SDK 👀

Последний заказ

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

В результате компании удаётся выжать из времени запуска очередное двукратное ускорение, постепенно уменьшающиеся до 1,3 раза или, что более вероятно, зафиксированные мной 1,2 раза.

Это хорошо. Однако ответственность за архитектуру критического пути запуска по-прежнему лежит на разработчике.

  • Профилируйте запуск с помощью инструмента App Launch и flame graph.
  • Оптимизируйте pre-main, осознанно используя статические библиотеки, динамические библиотеки, mergeable libraries.
  • Минимизируйте объём работы на критическом пути post-main.
  • Как можно быстрее отображайте начальный интерфейс, показывая только то, что действительно нужно пользователю.

На этом с запуском в iOS 27 всё. Увидимся в следующем году 👋

Источник

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Telegram

Популярное

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: