Otimizar os tempos de inicialização

Este documento fornece orientação aos parceiros para melhorar os tempos de inicialização de dispositivos Android específicos. O tempo de inicialização é um componente importante do desempenho do sistema, já que os usuários precisam esperar a inicialização ser concluída antes de usar o dispositivo. Para dispositivos como carros, em que a inicialização a frio acontece com mais frequência, ter um tempo de inicialização rápido é fundamental. Ninguém gosta de esperar dezenas de segundos apenas para inserir um destino de navegação.

O Android 8.0 reduz o tempo de inicialização com suporte a várias melhorias em vários componentes. A tabela a seguir resume essas melhorias de desempenho (como medido em dispositivos Google Pixel e Pixel XL).

Componente Melhoria
Carregador de inicialização
  • 1,6s foram economizados com a remoção do registro UART.
  • Economizou 0,4 segundos ao mudar de GZIP para LZ4
Kernel do dispositivo
  • Economizou 0,3s removendo configurações de kernel não usadas e reduzindo o tamanho do driver
  • 0,3s economizados com a otimização de pré-busca do dm-verity
  • 0,15 segundos foram economizados para remover a espera/teste desnecessário no driver.
  • 0,12s economizados para remover CONFIG_CC_OPTIMIZE_FOR_SIZE
Ajuste de E/S
  • 2 segundos economizados na inicialização normal
  • 25 segundos economizados na primeira inicialização
init.*.rc
  • 1,5s foram economizados com comandos de inicialização em paralelo
  • 0,25 segundos foram economizados ao iniciar o zygote antecipadamente
  • 0,22 segundos foram salvos com a opção cpuset tune
Animação de inicialização
  • Começou 2 segundos antes na inicialização sem o fsck acionado, muito maior na inicialização com o fsck acionado
  • 5 segundos foram economizados no Pixel XL com o desligamento imediato da animação de inicialização
Política do SELinux 0,2s foram salvos por genfscon

Otimizar o carregador de inicialização

Para otimizar o carregador de inicialização e melhorar os tempos de inicialização:

  • Para registro:
    • Desative a gravação de registro no UART, porque isso pode levar muito tempo com muita geração de registros. Em dispositivos Google Pixel, descobrimos que ele retarda o bootloader em 1,5s.
    • Registre apenas situações de erro e considere armazenar outras informações na memória com um mecanismo separado para extrair.
  • Para descompactação do kernel, considere usar LZ4 para hardware contemporâneo em vez de GZIP (exemplo de patch). Tenha em mente que diferentes opções de compactação do kernel podem ter tempos de carregamento e descompactação diferentes, e algumas opções podem funcionar melhor que outras para seu hardware específico.
  • Verifique tempos de espera desnecessários para a entrada de debouncing/modo especial e minimize eles.
  • O tempo de inicialização gasto no carregador de inicialização é transmitido ao kernel como cmdline.
  • Verifique o clock da CPU e considere a paralelização (requer suporte a vários núcleos) para carregar o kernel e inicializar a E/S.

Otimizar a eficiência de E/S

Melhorar a eficiência de E/S é fundamental para acelerar o tempo de inicialização, e a leitura de qualquer coisa que não seja necessária deve ser adiada até depois da inicialização.Em um Google Pixel, cerca de 1,2 GB de dados são lidos na inicialização.

Ajustar o sistema de arquivos

A leitura antecipada do kernel do Linux é ativada quando um arquivo é lido desde o início ou quando os blocos são lidos sequencialmente, tornando necessário ajustar os parâmetros do programador de E/S especificamente para a inicialização, que tem uma caracterização de carga de trabalho diferente dos apps normais.

Os dispositivos compatíveis com atualizações contínuas (A/B) se beneficiam muito do ajuste do sistema de arquivos na primeira inicialização (por exemplo, 20 segundos no Google Pixel). Como exemplo, ajustamos os seguintes parâmetros para o 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
    ...

Diversos

  • Ative o tamanho de pré-carregamento de hash dm-verity usando a configuração do kernel DM_VERITY_HASH_PREFETCH_MIN_SIZE (o tamanho padrão é 128).
  • Para melhorar a estabilidade do sistema de arquivos e uma verificação forçada descartada que ocorre em cada inicialização, use a nova ferramenta de geração ext4 definindo TARGET_USES_MKE2FS em BoardConfig.mk.

Analisar E/S

Para entender as atividades de E/S durante a inicialização, use os dados de ftrace do kernel (também usados pelo systrace):

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

Para detalhar o acesso a arquivos para cada arquivo, faça as seguintes alterações no kernel (somente kernel de desenvolvimento, não use em kernels de produção):

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

Use os scripts abaixo para analisar o desempenho de inicialização.

  • system/extras/boottime_tools/bootanalyze/bootanalyze.py Meça o tempo de inicialização com uma análise detalhada das etapas importantes do processo de inicialização.
  • system/extras/boottime_tools/io_analysis/check_file_read.py boot_trace Fornece informações de acesso para cada arquivo.
  • system/extras/boottime_tools/io_analysis/check_io_trace_all.py boot_trace Fornece detalhes no nível do sistema.

Otimizar init.*.rc

O init é a ponte entre o kernel e o framework, e os dispositivos geralmente passam alguns segundos em diferentes estágios de inicialização.

Executar tarefas em paralelo

Embora o init atual do Android seja mais ou menos um processo de linha de execução única, ainda é possível realizar algumas tarefas em paralelo.

  • Execute comandos lentos em um serviço de script de shell e faça a associação mais tarde esperando por uma propriedade específica. O Android 8.0 oferece suporte a esse caso de uso com um novo comando wait_for_property.
  • Identifique operações lentas no init. O sistema registra o comando de inicialização exec/wait_for_prop ou qualquer ação que leva muito tempo (no Android 8.0, qualquer comando leva mais de 50 ms). Exemplo:
    init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms

    A análise desse registro pode indicar oportunidades de melhoria.

  • Inicie os serviços e ative os dispositivos periféricos no caminho crítico com antecedência. Por exemplo, alguns SOCs exigem a inicialização de serviços relacionados à segurança antes de iniciar o SurfaceFlinger. Analise o registro do sistema quando o ServiceManager retornar "wait for service". Isso geralmente é um sinal de que um serviço dependente precisa ser iniciado primeiro.
  • Remova todos os serviços e comandos não utilizados em init.*.rc. Qualquer coisa que não seja usada na iniciação inicial precisa ser adiada até a inicialização ser concluída.

Observação:o serviço de propriedade faz parte do processo de inicialização. Portanto, chamar setproperty durante a inicialização pode causar um longo atraso se a inicialização estiver ocupada em comandos integrados.

Usar o ajuste do programador

Use o ajuste do programador para inicialização antecipada. Exemplo de um 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

Alguns serviços podem precisar de um aumento de prioridade durante a inicialização. Exemplo:

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

Iniciar o Zygote antecipadamente

Dispositivos com criptografia baseada em arquivos podem iniciar o zygote mais cedo no acionador zygote-start. Por padrão, o zygote é iniciado na classe principal, muito depois de zygote-start. Ao fazer isso, permita que o zygote seja executado em todas as CPUs, já que a configuração incorreta de cpuset pode forçar o zygote a ser executado em CPUs específicas.

Desativar a economia de energia

Durante a inicialização do dispositivo, a configuração de economia de energia para componentes como o controlador de CPU e/ou UFS pode ser desativada.

Cuidado:a economia de energia precisa estar ativada no modo de carregador para eficiência.

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

Adiar a inicialização não crítica

A inicialização não crítica, como a ZRAM, pode ser adiada para boot_complete.

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

Otimizar a animação de inicialização

Use as dicas a seguir para otimizar a animação de inicialização.

Configurar a inicialização antecipada

O Android 8.0 permite iniciar a animação de inicialização mais cedo, antes de montar a partição userdata. No entanto, mesmo usando a nova cadeia de ferramentas ext4 no Android 8.0, o fsck ainda é acionado periodicamente por motivos de segurança, causando um atraso na inicialização do serviço de bootanimation.

Para iniciar a bootanimation mais cedo, divida a montagem do fstab em duas fases:

  • Na fase inicial, monte apenas as partições (como system/ e vendor/) que não exigem verificações de execução e inicie os serviços de animação de inicialização e as dependências dele, como servicemanager e surfaceflinger.
  • Na segunda fase, monte partições (como data/) que exigem verificações de execução.

A animação de inicialização será iniciada muito mais rápido (e em tempo constante), independentemente do fsck.

Limpar a tela

Depois de receber o sinal de saída, a bootanimation reproduz a última parte, cuja duração pode retardar o tempo de inicialização. Um sistema que inicializa rapidamente não precisa de animações longas, que podem ocultar as melhorias feitas. Recomendamos que você faça o ciclo de repetição e a finalização curtos.

Otimizar o SELinux

Use as dicas a seguir para otimizar o SELinux e melhorar os tempos de inicialização.

  • Use expressões regulares (regex) limpas. Uma regex malformada pode causar muita sobrecarga ao corresponder à política do SELinux para sys/devices em file_contexts. Por exemplo, o regex /sys/devices/.*abc.*(/.*)? força erroneamente uma verificação de todos os subdiretórios /sys/devices que contêm "abc", permitindo correspondências para /sys/devices/abc e /sys/devices/xyz/abc. Melhorar esse regex para /sys/devices/[^/]*abc[^/]*(/.*)? vai permitir uma correspondência apenas para /sys/devices/abc.
  • Mover os marcadores para genfscon. Esse recurso do SELinux transmite prefixos de correspondência de arquivos para o kernel no binário do SELinux, onde o kernel os aplica a sistemas de arquivos gerados pelo kernel. Isso também ajuda a corrigir arquivos criados pelo kernel com rótulos incorretos, evitando condições de corrida que podem ocorrer entre processos do espaço do usuário que tentam acessar esses arquivos antes da nova marcação.

Ferramentas e métodos

Use as ferramentas a seguir para coletar dados para as metas de otimização.

Bootchart

O Bootchart fornece um detalhamento da carga de CPU e E/S de todos os processos do sistema. Ele não requer a reconstrução da imagem do sistema e pode ser usado como uma verificação rápida antes de mergulhar no systrace.

Para ativar o bootchart:

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

Depois da inicialização, busque o gráfico de inicialização:

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

Quando terminar, exclua /data/bootchart/enabled para evitar a coleta dos dados sempre.

Se o bootchart não funcionar e você receber um erro informando que bootchart.png não existe, faça o seguinte:
  1. Execute os seguintes 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. Atualize $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh para apontar para a cópia local de pybootchartgui (localizada em ~/Documents/bootchart/pybootchartgui.py)

Systrace

O Systrace permite coletar rastros do kernel e do Android durante a inicialização. A visualização do systrace pode ajudar na análise de problemas específicos durante a inicialização. No entanto, para verificar o número médio ou acumulado durante toda a inicialização, é mais fácil analisar o rastreamento do kernel diretamente.

Para ativar o systrace durante a inicialização:

  • Em frameworks/native/cmds/atrace/atrace.rc, mude:
      write /sys/kernel/debug/tracing/tracing_on 0
      write /sys/kernel/tracing/tracing_on 0

    Para:

      #    write /sys/kernel/debug/tracing/tracing_on 0
      #    write /sys/kernel/tracing/tracing_on 0
  • Isso ativa o rastreamento, que fica desativado por padrão.

  • No arquivo device.mk, adicione esta linha:
    PRODUCT_PROPERTY_OVERRIDES +=    debug.atrace.tags.enableflags=802922
    PRODUCT_PROPERTY_OVERRIDES +=    persist.traced.enable=0
  • No arquivo BoardConfig.mk do dispositivo, adicione o seguinte:
    BOARD_KERNEL_CMDLINE := ... trace_buf_size=64M trace_event=sched_wakeup,sched_switch,sched_blocked_reason,sched_cpu_hotplug
  • Para uma análise detalhada de E/S, adicione também o bloco e ext4 e f2fs.

  • No arquivo init.rc específico do dispositivo, adicione o seguinte:
    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
  • Depois da inicialização, busque o rastro:

    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