Как оптимизировать время загрузки

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

В Android 8.0 время загрузки сокращено за счет улучшений в разных компонентах. В таблице ниже приведены данные об улучшении производительности (на основе измерений на устройствах Google Pixel и Pixel XL).

Компонент Улучшение
Загрузчик
  • Удаление журнала UART позволило сэкономить 1,6 с.
  • Переход с GZIP на LZ4 позволил сэкономить 0,4 секунды.
Ядро устройства
  • Удаление неиспользуемых конфигураций ядра и уменьшение размера драйвера позволило сэкономить 0,3 секунды.
  • Сэкономлено 0,3 секунды благодаря оптимизации предварительной выборки dm-verity
  • Сэкономлено 0,15 с на удалении ненужного ожидания/теста в драйвере
  • Удаление CONFIG_CC_OPTIMIZE_FOR_SIZE сэкономило 0,12 секунды.
Настройка ввода-вывода
  • При обычной загрузке экономия составила 2 секунды.
  • Первая загрузка стала на 25 секунд быстрее
init.*.rc
  • Сэкономлено 1,5 с за счет параллельного выполнения команд инициализации
  • Экономия 0,25 с за счет раннего запуска zygote
  • Экономия времени благодаря настройке cpuset: 0,22 с
Анимированная заставка при запуске
  • Запуск на 2 секунды раньше при загрузке без fsck, намного больше при загрузке с fsck.
  • На Pixel XL время загрузки сократилось на 5 секунд за счет немедленного отключения анимации загрузки.
Правила SELinux Сохранено 0,2 с с помощью genfscon

Оптимизация загрузчика

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

  • Для ведения журналов:
    • Отключите запись журнала в UART, так как при большом количестве записей это может занять много времени. На устройствах Google Pixel это замедляет загрузчик на 1, 5 секунды.
    • Регистрируйте только ошибки и сохраняйте другую информацию в памяти с помощью отдельного механизма извлечения.
  • Для распаковки ядра на современном оборудовании рекомендуется использовать LZ4 вместо GZIP (пример патча). Учтите, что разные варианты сжатия ядра могут иметь разное время загрузки и распаковки, и некоторые варианты могут работать лучше, чем другие, для вашего оборудования.
  • Проверьте, не слишком ли долго устройство ждет перед переходом в специальный режим или устранением дребезга, и сократите это время.
  • Передавать время загрузки, потраченное в загрузчике операционной системы, в ядро в виде cmdline.
  • Проверьте тактовую частоту ЦП и рассмотрите возможность распараллеливания (требуется поддержка многоядерности) для загрузки ядра и инициализации I/O.

Оптимизация эффективности ввода-вывода

Повышение эффективности I/O имеет решающее значение для ускорения загрузки. Все, что не нужно для загрузки, следует отложить на более поздний этап. На устройстве Google Pixel при загрузке считывается около 1,2 ГБ данных.

Настройте файловую систему

Упреждающее чтение ядра Linux запускается, когда файл считывается с начала или когда блоки считываются последовательно, что требует настройки параметров планировщика I/O специально для загрузки (которая имеет другие характеристики рабочей нагрузки, чем обычные приложения).

Устройства, поддерживающие параллельные обновления (A/B), получают значительное преимущество от настройки файловой системы при первой загрузке (например, 20 секунд на Google Pixel). Например, для Google Pixel мы настроили следующие параметры:

on late-fs
  # boot time fs tune
    # boot time fs tune
    write /sys/block/sda/queue/iostats 0
    write /sys/block/sda/queue/scheduler cfq
    write /sys/block/sda/queue/iosched/slice_idle 0
    write /sys/block/sda/queue/read_ahead_kb 2048
    write /sys/block/sda/queue/nr_requests 256
    write /sys/block/dm-0/queue/read_ahead_kb 2048
    write /sys/block/dm-1/queue/read_ahead_kb 2048

on property:sys.boot_completed=1
    # end boot time fs tune
    write /sys/block/sda/queue/read_ahead_kb 512
    ...

Другое

  • Включите размер предварительной выборки хеша dm-verity, используя конфигурацию ядра DM_VERITY_HASH_PREFETCH_MIN_SIZE (размер по умолчанию – 128).
  • Чтобы повысить стабильность файловой системы и отказаться от принудительной проверки, которая выполняется при каждой загрузке, используйте новый инструмент создания ext4, задав значение TARGET_USES_MKE2FS в файле BoardConfig.mk.

Анализ ввода-вывода

Чтобы понять, какие операции ввода-вывода выполняются во время загрузки, используйте данные ftrace ядра (они также используются systrace):

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

Чтобы разбить доступ к файлам для каждого файла, внесите следующие изменения в ядро (только в ядро разработки; не используйте в производственных ядрах):

diff --git a/fs/open.c b/fs/open.c
index 1651f35..a808093 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -981,6 +981,25 @@
 }
 EXPORT_SYMBOL(file_open_root);
 
+static void _trace_do_sys_open(struct file *filp, const struct open_how *how, long fd)
+{
+       char *buf;
+       char *fname;
+
+       buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+       if (!buf)
+               return;
+       fname = d_path(&filp->f_path, buf, PAGE_SIZE);
+
+       if (IS_ERR(fname))
+               goto out;
+
+       trace_printk("%s: open(\"%s\", %d, %d) fd = %ld, inode = %ld\n",
+                     current->comm, fname, how->flags, how->mode, fd, filp->f_inode->i_ino);
+out:
+       kfree(buf);
+}
+
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
 {
 	struct open_flags op;
@@ -1003,6 +1022,7 @@
 		} else {
 			fsnotify_open(f);
 			fd_install(fd, f);
+			_trace_do_sys_open(f, flags, mode, fd);

Используйте приведенные ниже скрипты, чтобы анализировать производительность при загрузке.

  • system/extras/boottime_tools/bootanalyze/bootanalyze.py Измеряет время загрузки с разбивкой по важным этапам процесса загрузки.
  • system/extras/boottime_tools/io_analysis/check_file_read.py boot_trace Предоставляет информацию о доступе к каждому файлу.
  • system/extras/boottime_tools/io_analysis/check_io_trace_all.py boot_trace – разбивка на уровне системы.

Оптимизация init.*.rc

Init – это мост от ядра до создания фреймворка. Обычно устройства проводят несколько секунд на разных этапах инициализации.

Параллельное выполнение задач

Хотя текущий процесс инициализации Android в основном однопоточный, некоторые задачи можно выполнять параллельно.

  • Выполняйте медленные команды в сервисе сценариев командной строки и присоединяйте их позже, ожидая определенного свойства. В Android 8.0 для этого случая предусмотрена новая команда wait_for_property.
  • Как определить медленные операции в init Система регистрирует команду init exec/wait_for_prop или любое действие, занимающее много времени (в Android 8.0 – любую команду, выполнение которой занимает более 50 мс). Пример:
    init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms

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

  • Запускайте сервисы и включайте периферийные устройства на ранних этапах критического пути. Например, некоторые SOC требуют запуска сервисов, связанных с безопасностью, до запуска SurfaceFlinger. Если ServiceManager возвращает сообщение "wait for service" (ожидание сервиса), проверьте системный журнал. Обычно это означает, что сначала нужно запустить зависимый сервис.
  • Удалите все неиспользуемые сервисы и команды в init.*.rc. Все, что не используется на раннем этапе инициализации, должно быть отложено до завершения загрузки.

Примечание. Сервис Property является частью процесса init, поэтому вызов setproperty во время загрузки может привести к длительной задержке, если init занят встроенными командами.

Как использовать настройку планировщика

Используйте настройку планировщика для ранней загрузки. Пример для Google Pixel:

on init
    # boottime stune
    write /dev/stune/schedtune.prefer_idle 1
    write /dev/stune/schedtune.boost 100
    on property:sys.boot_completed=1
    # reset stune
    write /dev/stune/schedtune.prefer_idle 0
    write /dev/stune/schedtune.boost 0

    # or just disable EAS during boot
    on init
    write /sys/kernel/debug/sched_features NO_ENERGY_AWARE
    on property:sys.boot_completed=1
    write /sys/kernel/debug/sched_features ENERGY_AWARE

Некоторым сервисам может потребоваться повышенный приоритет во время загрузки. Пример:

init.zygote64.rc:
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
...

Ранний запуск Zygote

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

Как отключить энергосбережение

Во время загрузки устройства можно отключить режим энергосбережения для таких компонентов, как UFS и/или регулятор ЦП.

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

on init
    # Disable UFS powersaving
    write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 0
    write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 0
    write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 0
    write /sys/module/lpm_levels/parameters/sleep_disabled Y
on property:sys.boot_completed=1
    # Enable UFS powersaving
    write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 1
    write /sys/module/lpm_levels/parameters/sleep_disabled N
on charger
    # Enable UFS powersaving
    write /sys/devices/soc/${ro.boot.bootdevice}/clkscale_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/clkgate_enable 1
    write /sys/devices/soc/${ro.boot.bootdevice}/hibern8_on_idle_enable 1
    write /sys/class/typec/port0/port_type sink
    write /sys/module/lpm_levels/parameters/sleep_disabled N

Отложить инициализацию некритических функций

Необязательную инициализацию, например ZRAM, можно отложить до boot_complete.

on property:sys.boot_completed=1
   # Enable ZRAM on boot_complete
   swapon_all /vendor/etc/fstab.${ro.hardware}

Как оптимизировать анимацию загрузки

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

Как настроить ранний старт

В Android 8.0 анимацию загрузки можно запускать раньше, до монтирования раздела userdata. Однако даже при использовании новой цепочки инструментов ext4 в Android 8.0 fsck по-прежнему периодически запускается из соображений безопасности, что приводит к задержке запуска службы bootanimation.

Чтобы анимация загрузки запускалась раньше, разделите монтирование fstab на два этапа:

  • На раннем этапе монтируйте только разделы (например, system/ и vendor/), которые не требуют проверок во время выполнения, а затем запустите сервисы анимации загрузки и их зависимости (например, servicemanager и surfaceflinger).
  • На втором этапе смонтируйте разделы (например, data/), для которых требуется выполнить проверки.

Анимация загрузки будет запускаться намного быстрее (и всегда в одно и то же время) независимо от fsck.

Завершить очистку

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

Как оптимизировать SELinux

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

  • Используйте понятные регулярные выражения. Некорректно составленные регулярные выражения могут привести к значительным накладным расходам при сопоставлении правил SELinux для sys/devices в file_contexts. Например, регулярное выражение /sys/devices/.*abc.*(/.*)? ошибочно заставляет сканировать все подкаталоги /sys/devices, содержащие "abc", что позволяет находить совпадения как для /sys/devices/abc, так и для /sys/devices/xyz/abc. Если изменить регулярное выражение на /sys/devices/[^/]*abc[^/]*(/.*)?, то соответствие будет найдено только для /sys/devices/abc.
  • Переместите ярлыки в genfscon. Эта функция SELinux передает префиксы, соответствующие файлам, в ядро в двоичном файле SELinux, где ядро применяет их к файловым системам, созданным ядром. Это также помогает исправить неправильно помеченные файлы, созданные ядром, предотвращая состояние гонки, которое может возникнуть между процессами пользовательского пространства, пытающимися получить доступ к этим файлам до того, как произойдет повторная маркировка.

Инструменты и методы

Чтобы собирать данные для целей оптимизации, используйте следующие инструменты:

Bootchart

Bootchart предоставляет информацию о загрузке ЦП и вводе-выводе для всех процессов в системе. Для этого не нужно пересобирать образ системы. Это можно сделать быстро, чтобы проверить работоспособность системы перед использованием systrace.

Чтобы включить bootchart:

adb shell 'touch /data/bootchart/enabled'
adb reboot

После загрузки получите диаграмму загрузки:

$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh

После этого удалите /data/bootchart/enabled, чтобы данные не собирались каждый раз.

Если bootchart не работает и вы получаете сообщение об ошибке, в котором говорится, что bootchart.png не существует, выполните следующие действия:
  1. Выполните следующие команды:
          sudo apt install python-is-python3
          cd ~/Documents
          git clone https://github.com/xrmx/bootchart.git
          cd bootchart/pybootchartgui
          mv main.py.in main.py
        
  2. Измените $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh, чтобы он указывал на локальную копию pybootchartgui (находящуюся в ~/Documents/bootchart/pybootchartgui.py).

Systrace

Systrace позволяет собирать данные трассировки ядра и Android во время загрузки. Визуализация данных systrace помогает анализировать определенные проблемы, возникающие при загрузке. Однако, чтобы проверить среднее или общее количество за всю загрузку, проще посмотреть трассировку ядра напрямую.

Чтобы включить systrace во время загрузки:

  • В frameworks/native/cmds/atrace/atrace.rc измените:
      write /sys/kernel/debug/tracing/tracing_on 0
      write /sys/kernel/tracing/tracing_on 0

    Кому:

      #    write /sys/kernel/debug/tracing/tracing_on 0
      #    write /sys/kernel/tracing/tracing_on 0
  • Это позволит включить отслеживание (по умолчанию оно отключено).

  • Добавьте в файл device.mk следующую строку:
    PRODUCT_PROPERTY_OVERRIDES +=    debug.atrace.tags.enableflags=802922
    PRODUCT_PROPERTY_OVERRIDES +=    persist.traced.enable=0
  • В файл BoardConfig.mk устройства добавьте следующее:
    BOARD_KERNEL_CMDLINE := ... trace_buf_size=64M trace_event=sched_wakeup,sched_switch,sched_blocked_reason,sched_cpu_hotplug
  • Для подробного анализа операций ввода-вывода также добавьте block, ext4 и f2fs.

  • Добавьте в файл init.rc для устройства следующий код:
    on property:sys.boot_completed=1          // This stops tracing on boot complete
    write /d/tracing/tracing_on 0
    write /d/tracing/events/ext4/enable 0
    write /d/tracing/events/f2fs/enable 0
    write /d/tracing/events/block/enable 0
  • После загрузки получите трассировку:

    adb root && adb shell atrace --async_stop -z -c -o /data/local/tmp/boot_trace
    adb pull /data/local/tmp/boot_trace
    $ANDROID_BUILD_TOP/external/chromium-trace/systrace.py --from-file=boot_trace