Cómo optimizar los tiempos de inicio

En este documento, se proporciona orientación a los socios para mejorar los tiempos de inicio de dispositivos Android específicos. El tiempo de inicio es un componente importante del rendimiento del sistema, ya que los usuarios deben esperar a que se complete el inicio antes de poder usar el dispositivo. En el caso de los dispositivos, como los automóviles, en los que el inicio en frío ocurre con más frecuencia, es fundamental tener un tiempo de inicio rápido (a nadie le gusta esperar decenas de segundos solo para ingresar un destino de navegación).

Android 8.0 permite reducir los tiempos de inicio, ya que admite varias mejoras en una variedad de componentes. En la siguiente tabla, se resumen estas mejoras de rendimiento (medidas en dispositivos Google Pixel y Pixel XL).

Componente Mejora
Bootloader
  • Se ahorraron 1.6 s quitando el registro de UART.
  • Se ahorraron 0.4 s cambiando de GZIP a LZ4
Kernel del dispositivo
  • Se ahorraron 0.3 s quitando las configuraciones de kernel que no se usaban y reduciendo el tamaño del controlador.
  • Se ahorraron 0.3 s con la optimización de la obtención previa de dm-verity
  • Se ahorraron 0.15 s para quitar la espera o prueba innecesaria en el controlador.
  • Se ahorraron 0.12 s para quitar CONFIG_CC_OPTIMIZE_FOR_SIZE.
Ajuste de E/S
  • Se ahorraron 2 segundos en el inicio normal.
  • Se ahorraron 25 s en el primer inicio.
init.*.rc
  • Se ahorraron 1.5 s con la ejecución en paralelo de los comandos init
  • Se ahorraron 0.25 s iniciando el zygote antes
  • Se ahorraron 0.22 s con la optimización de cpuset
Animación de inicio
  • Se inició 2 segundos antes en el inicio sin activar fsck, mucho más grande en el inicio con el inicio activado de fsck.
  • Se ahorraron 5 segundos en el Pixel XL con el cierre inmediato de la animación de inicio.
Política de SELinux Se ahorró 0.2 s con genfscon

Optimiza el bootloader

Para optimizar el bootloader y mejorar los tiempos de inicio, haz lo siguiente:

  • Para el registro:
    • Inhabilita la escritura de registros en UART, ya que puede tardar mucho tiempo con muchos registros. (en los dispositivos Google Pixel, descubrimos que ralentiza el bootloader 1.5 s).
    • Registra solo las situaciones de error y considera almacenar otra información en la memoria con un mecanismo independiente para recuperarla.
  • Para la descompresión del kernel, considera usar LZ4 para el hardware contemporáneo en lugar de GZIP (parche de ejemplo). Ten en cuenta que las diferentes opciones de compresión del kernel pueden tener diferentes tiempos de carga y descompresión, y algunas opciones pueden funcionar mejor que otras para tu hardware específico.
  • Verifica los tiempos de espera innecesarios para la entrada de depuración o el modo especial y minímalos.
  • Pasa el tiempo de inicio que se usó en el bootloader al kernel como cmdline.
  • Verifica el reloj de la CPU y considera la paralelización (requiere compatibilidad con varios núcleos) para la carga del kernel y la inicialización de la E/S.

Optimiza la eficiencia de E/S

Mejorar la eficiencia de E/S es fundamental para acelerar el tiempo de inicio, y la lectura de cualquier elemento que no sea necesario debe diferirse hasta después del inicio (en un Google Pixel, se leen alrededor de 1.2 GB de datos durante el inicio).

Ajusta el sistema de archivos

La lectura anticipada del kernel de Linux se activa cuando se lee un archivo desde el principio o cuando se leen bloques de forma secuencial, lo que hace necesario ajustar los parámetros del programador de E/S específicamente para el inicio (que tiene una caracterización de carga de trabajo diferente a la de las apps normales).

Los dispositivos que admiten actualizaciones sin interrupciones (A/B) se benefician en gran medida de la optimización del sistema de archivos durante el primer inicio (p.ej., 20 s en Google Pixel). A modo de ejemplo, ajustamos los siguientes parámetros para el 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
    ...

Varios

  • Activa el tamaño de la precarga de hash de dm-verity con la configuración del kernel DM_VERITY_HASH_PREFETCH_MIN_SIZE (el tamaño predeterminado es 128).
  • Para obtener una mejor estabilidad del sistema de archivos y una verificación forzada que se produce en cada inicio, usa la nueva herramienta de generación de ext4 configurando TARGET_USES_MKE2FS en BoardConfig.mk.

Cómo analizar la E/S

Para comprender las actividades de E/S durante el inicio, usa los datos de ftrace del kernel (que también usa systrace):

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

Para desglosar el acceso a los archivos de cada archivo, realiza los siguientes cambios en el kernel (solo kernel de desarrollo, no lo uses en kernels de producción):

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);

Usa las siguientes secuencias de comandos para analizar el rendimiento del inicio.

  • system/extras/boottime_tools/bootanalyze/bootanalyze.py Mide el tiempo de inicio con un desglose de los pasos importantes del proceso de inicio.
  • system/extras/boottime_tools/io_analysis/check_file_read.py boot_trace Proporciona información de acceso para cada archivo.
  • system/extras/boottime_tools/io_analysis/check_io_trace_all.py boot_trace Proporciona un desglose a nivel del sistema.

Optimiza init.*.rc

Init es el puente desde el kernel hasta que se establece el framework, y los dispositivos suelen pasar unos segundos en diferentes etapas de init.

Ejecuta tareas en paralelo

Si bien el inicio de Android actual es más o menos un proceso de subproceso único, aún puedes realizar algunas tareas en paralelo.

  • Ejecuta comandos lentos en un servicio de secuencia de comandos de shell y únete a él más adelante esperando una propiedad específica. Android 8.0 admite este caso de uso con un nuevo comando wait_for_property.
  • Identifica las operaciones lentas en init. El sistema registra el comando init exec/wait_for_prop o cualquier acción que tarde mucho tiempo (en Android 8.0, cualquier comando que tarde más de 50 ms). Por ejemplo:
    init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms

    Revisar este registro puede indicar oportunidades de mejora.

  • Inicia los servicios y habilita los dispositivos periféricos en la ruta crítica con anticipación. Por ejemplo, algunos SOC requieren iniciar servicios relacionados con la seguridad antes de iniciar SurfaceFlinger. Revisa el registro del sistema cuando ServiceManager muestra el mensaje "wait for service". Por lo general, esto es una señal de que primero se debe iniciar un servicio dependiente.
  • Quita los servicios y comandos sin usar en init.*.rc. Todo lo que no se use en la inicialización de la etapa inicial se debe aplazar hasta que se complete el inicio.

Nota: El servicio de propiedades forma parte del proceso de init, por lo que llamar a setproperty durante el inicio puede generar una demora prolongada si init está ocupado en comandos integrados.

Usa el ajuste del programador

Usa el ajuste del programador para el inicio anticipado. Ejemplo de un 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

Es posible que algunos servicios necesiten un aumento de prioridad durante el inicio. Ejemplo:

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

Cómo iniciar el zygote antes

Los dispositivos con encriptación basada en archivos pueden iniciar el zygote antes en el activador zygote-start (de forma predeterminada, el zygote se inicia en la clase principal, que es mucho más tarde que zygote-start). Cuando lo hagas, asegúrate de permitir que el zygote se ejecute en todas las CPUs (ya que la configuración incorrecta de cpuset puede forzar al zygote a ejecutarse en CPUs específicas).

Inhabilita el ahorro de energía

Durante el inicio del dispositivo, se puede inhabilitar la configuración de ahorro de energía para componentes como el regulador de UFS o CPU.

Precaución: Para mejorar la eficiencia, se debe habilitar el ahorro de energía en el modo de cargador.

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

Diferir la inicialización no crítica

La inicialización no crítica, como ZRAM, se puede diferir a boot_complete.

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

Cómo optimizar la animación de inicio

Usa las siguientes sugerencias para optimizar la animación de inicio.

Cómo configurar el inicio anticipado

Android 8.0 habilita el inicio de la animación de inicio antes, antes de activar la partición de datos del usuario. Sin embargo, incluso cuando se usa la nueva cadena de herramientas ext4 en Android 8.0, fsck se activa de forma periódica por motivos de seguridad, lo que causa una demora en el inicio del servicio de bootanimation.

Para que bootanimation se inicie antes, divide el montaje de fstab en dos fases:

  • En la fase inicial, activa solo las particiones (como system/ y vendor/) que no requieran verificaciones de ejecución y, luego, inicia los servicios de animación de inicio y sus dependencias (como servicemanager y surfaceflinger).
  • En la segunda fase, activa las particiones (como data/) que sí requieren verificaciones de ejecución.

La animación de inicio se iniciará mucho más rápido (y en un tiempo constante) independientemente de fsck.

Termina con limpieza

Después de recibir la señal de salida, bootanimation reproduce la última parte, cuya duración puede ralentizar el tiempo de inicio. Un sistema que se inicia rápidamente no necesita animaciones largas que podrían ocultar de manera eficaz cualquier mejora realizada. Te recomendamos que hagas que el bucle de repetición y el final sean breves.

Optimiza SELinux

Usa las siguientes sugerencias para optimizar SELinux y mejorar los tiempos de inicio.

  • Usa expresiones regulares (regex) claras. Las regex con el formato incorrecto pueden generar mucha sobrecarga cuando coinciden con la política de SELinux para sys/devices en file_contexts. Por ejemplo, la regex /sys/devices/.*abc.*(/.*)? fuerza por error un análisis de todos los subdirectorios /sys/devices que contienen "abc", lo que habilita las coincidencias para /sys/devices/abc y /sys/devices/xyz/abc. Mejorar esta regex a /sys/devices/[^/]*abc[^/]*(/.*)? habilitará una coincidencia solo para /sys/devices/abc.
  • Mueve las etiquetas a genfscon. Esta función existente de SELinux pasa prefijos que coinciden con archivos al kernel en el objeto binario de SELinux, donde el kernel los aplica a los sistemas de archivos generados por el kernel. Esto también ayuda a corregir los archivos creados por el kernel con etiquetas incorrectas, lo que evita las condiciones de carrera que pueden ocurrir entre los procesos del espacio de usuario que intentan acceder a estos archivos antes de que se vuelva a etiquetar.

Herramientas y métodos

Usa las siguientes herramientas para recopilar datos para los objetivos de optimización.

Bootchart

Bootchart proporciona un desglose de la carga de CPU y E/S de todos los procesos del sistema completo. No requiere volver a compilar la imagen del sistema y se puede usar como una verificación rápida antes de comenzar a usar systrace.

Para habilitar bootchart, haz lo siguiente:

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

Después del inicio, recupera el gráfico de inicio:

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

Cuando termines, borra /data/bootchart/enabled para evitar que se recopilen los datos cada vez.

Si bootchart no funciona y recibes un error que indica que bootchart.png no existe, haz lo siguiente:
  1. Ejecuta los siguientes comandos:
          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. Actualiza $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh para que apunte a la copia local de pybootchartgui (ubicada en ~/Documents/bootchart/pybootchartgui.py).

Systrace

Systrace permite recopilar registros del kernel y de Android durante el inicio. La visualización de systrace puede ayudar a analizar problemas específicos durante el inicio. (Sin embargo, para verificar la cantidad promedio o la cantidad acumulada durante todo el inicio, es más fácil observar el registro del kernel directamente).

Para habilitar systrace durante el inicio, haz lo siguiente:

  • En frameworks/native/cmds/atrace/atrace.rc, cambia lo siguiente:
      write /sys/kernel/debug/tracing/tracing_on 0
      write /sys/kernel/tracing/tracing_on 0

    A:

      #    write /sys/kernel/debug/tracing/tracing_on 0
      #    write /sys/kernel/tracing/tracing_on 0
  • Esto habilita el seguimiento (que está inhabilitado de forma predeterminada).

  • En el archivo device.mk, agrega la siguiente línea:
    PRODUCT_PROPERTY_OVERRIDES +=    debug.atrace.tags.enableflags=802922
    PRODUCT_PROPERTY_OVERRIDES +=    persist.traced.enable=0
  • En el archivo BoardConfig.mk del dispositivo, agrega lo siguiente:
    BOARD_KERNEL_CMDLINE := ... trace_buf_size=64M trace_event=sched_wakeup,sched_switch,sched_blocked_reason,sched_cpu_hotplug
  • Para obtener un análisis detallado de E/S, también agrega bloque, ext4 y f2fs.

  • En el archivo init.rc específico del dispositivo, agrega lo siguiente:
    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
  • Después del inicio, recupera el seguimiento:

    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