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

В этом документе представлены рекомендации для партнеров по сокращению времени загрузки определенных устройств 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 секунд при первой загрузке
инициализация.*.rc
  • Сэкономлено 1,5 секунды благодаря распараллеливанию команд инициализации.
  • Сэкономлено 0,25 с за счет раннего запуска зиготы
  • Сэкономлено 0,22 с за счет настройки процессора
Загрузочная анимация
  • Запускается на 2 секунды раньше при загрузке без запуска fsck, намного больше при загрузке с запуском fsck
  • Сэкономлено 5 секунд на Pixel XL с немедленным отключением загрузочной анимации
Политика SELinux Сэкономлено 0,2 с благодаря genfscon

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

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

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

Оптимизация ядра

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

Сворачивание конфигурации устройства

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

  • Определите неиспользуемые драйверы . Просмотрите каталоги /dev и /sys и найдите узлы с общими метками SELinux (что указывает на то, что эти узлы не настроены для доступа из пользовательского пространства). Удалите все такие узлы, если они найдены.
  • Отмените неиспользуемые КОНФИГУРАЦИИ . Просмотрите файл .config, сгенерированный сборкой ядра, чтобы явно отменить все неиспользуемые настройки CONFIG, которые были включены по умолчанию. Например, мы удалили следующие неиспользуемые КОНФИГУРАЦИИ из Google Pixel:
    CONFIG_ANDROID_LOGGER=y
    CONFIG_IMX134=y
    CONFIG_IMX132=y
    CONFIG_OV9724=y
    CONFIG_OV5648=y
    CONFIG_GC0339=y
    CONFIG_OV8825=y
    CONFIG_OV8865=y
    CONFIG_s5k4e1=y
    CONFIG_OV12830=y
    CONFIG_USB_EHCI_HCD=y
    CONFIG_IOMMU_IO_PGTABLE_FAST_SELFTEST=y
    CONFIG_IKCONFIG=y
    CONFIG_RD_BZIP2=y
    CONFIG_RD_LZMA=y
    CONFIG_TI_DRV2667=y
    CONFIG_CHR_DEV_SCH=y
    CONFIG_MMC=y
    CONFIG_MMC_PERF_PROFILING=y
    CONFIG_MMC_CLKGATE=y
    CONFIG_MMC_PARANOID_SD_INIT=y
    CONFIG_MMC_BLOCK_MINORS=32
    CONFIG_MMC_TEST=y
    CONFIG_MMC_SDHCI=y
    CONFIG_MMC_SDHCI_PLTFM=y
    CONFIG_MMC_SDHCI_MSM=y
    CONFIG_MMC_SDHCI_MSM_ICE=y
    CONFIG_MMC_CQ_HCI=y
    CONFIG_MSDOS_FS=y
    # CONFIG_SYSFS_SYSCALL is not set
    CONFIG_EEPROM_AT24=y
    # CONFIG_INPUT_MOUSEDEV_PSAUX is not set
    CONFIG_INPUT_HBTP_INPUT=y
    # CONFIG_VGA_ARB is not set
    CONFIG_USB_MON=y
    CONFIG_USB_STORAGE_DATAFAB=y
    CONFIG_USB_STORAGE_FREECOM=y
    CONFIG_USB_STORAGE_ISD200=y
    CONFIG_USB_STORAGE_USBAT=y
    CONFIG_USB_STORAGE_SDDR09=y
    CONFIG_USB_STORAGE_SDDR55=y
    CONFIG_USB_STORAGE_JUMPSHOT=y
    CONFIG_USB_STORAGE_ALAUDA=y
    CONFIG_USB_STORAGE_KARMA=y
    CONFIG_USB_STORAGE_CYPRESS_ATACB=y
    CONFIG_SW_SYNC_USER=y
    CONFIG_SEEMP_CORE=y
    CONFIG_MSM_SMEM_LOGGING=y
    CONFIG_IOMMU_DEBUG=y
    CONFIG_IOMMU_DEBUG_TRACKING=y
    CONFIG_IOMMU_TESTS=y
    CONFIG_MOBICORE_DRIVER=y
    # CONFIG_DEBUG_PREEMPT is not set
    
    .
  • Удалите CONFIG, которые приводят к ненужным тестовым запускам при каждой загрузке . Хотя такие конфигурации полезны при разработке, такие конфигурации (например, CONFIG_IOMMU_IO_PGTABLE_FAST_SELFTEST) должны быть удалены из производственного ядра.

Минимизация размера драйвера

Некоторые драйверы в ядре устройства могут быть удалены, если функция не используется для дальнейшего уменьшения размера ядра. Например, если WLAN подключена через PCIe, поддержка SDIO не используется и должна быть удалена во время компиляции. Для получения дополнительной информации см. параметр ядра Google Pixel: net: wireless: cnss: для отключения поддержки SDIO.

Удаление оптимизации компилятора по размеру

Удалите конфигурацию ядра для CONFIG_CC_OPTIMIZE_FOR_SIZE. Этот флаг был первоначально введен, когда предполагалось, что меньший размер кода приведет к горячему попаданию в кэш (и, следовательно, будет быстрее). Однако это предположение больше не работает, поскольку современные мобильные SoC стали более мощными.

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

Откладывание инициализации

Многие процессы запускаются во время загрузки, но только компоненты в критическом пути (загрузчик > ядро ​​> инициализация > монтирование файловой системы > zygote > системный сервер) напрямую влияют на время загрузки. Профилируйте initcall во время загрузки ядра, чтобы определить периферийные устройства/компоненты, которые работают медленно и не являются критическими для запуска процесса инициализации, а затем отложите эти периферийные устройства/компоненты до более позднего этапа загрузки, переместив их в загружаемые модули ядра. Переход к асинхронному зондированию устройства/драйвера также может помочь распараллелить медленные компоненты в ядре > критический путь инициализации.

BoardConfig-common.mk:
    BOARD_KERNEL_CMDLINE += initcall_debug ignore_loglevel

driver:
    .probe_type = PROBE_PREFER_ASYNCHRONOUS,

Примечание. Зависимости драйверов необходимо разрешать осторожно, добавляя поддержку EPROBEDEFER .

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

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

Настройка файловой системы

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

Устройства, которые поддерживают бесшовные (A/B) обновления, значительно выигрывают от настройки файловой системы при первой загрузке (например, 20s в 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

Инициализация — это мост от ядра до установки фреймворка, и устройства обычно тратят несколько секунд на разные этапы инициализации.

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

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

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

Системная трасса

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