Оптимизируйте время загрузки

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

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

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

Оптимизировать загрузчик

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

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

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

Повышение эффективности ввода-вывода имеет решающее значение для ускорения загрузки, а чтение всего ненужного следует отложить до окончания загрузки (в Google Pixel при загрузке считывается около 1,2 ГБ данных).

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

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

Устройства, поддерживающие плавные (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, int flags, int mode, 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, flags, 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 возвращает «ожидание обслуживания» — обычно это признак того, что сначала необходимо запустить зависимую службу.
  • Удалите все неиспользуемые службы и команды из init.*.rc. Все, что не используется на ранней стадии инициализации, должно быть отложено до завершения загрузки.

Примечание. Служба свойств является частью процесса инициализации, поэтому вызов 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-start (по умолчанию зигота запускается в классе main, что намного позже, чем zygote-start). При этом обязательно разрешите запуск zygote на всех процессорах (так как неправильная настройка процессорного набора может привести к запуску 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 позволяет запускать загрузочную анимацию заранее, до монтирования раздела пользовательских данных. Однако даже при использовании новой цепочки инструментов ext4 в Android 8.0 fsck по-прежнему периодически запускается из соображений безопасности, что приводит к задержке запуска службы загрузочной анимации.

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

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

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

Закончите чистоту

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

Оптимизировать SELinux

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

  • Используйте чистые регулярные выражения (regex) . Плохо сформированное регулярное выражение может привести к большим накладным расходам при сопоставлении политики 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 обеспечивает разбивку загрузки ЦП и ввода-вывода всех процессов для всей системы. Он не требует перестройки образа системы и может использоваться для быстрой проверки работоспособности перед погружением в systrace.

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

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

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

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

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

Если загрузочная диаграмма не работает и вы получаете сообщение об ошибке, сообщающее, что 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 позволяет собирать трассировку ядра и 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
  • Для детального анализа ввода-вывода также добавьте блок, 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