Otimizando tempos de inicialização

Este documento fornece orientações 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, pois os usuários devem aguardar a conclusão da inicialização antes de poderem usar o dispositivo. Para dispositivos como carros, onde a inicialização a frio ocorre com mais frequência, é fundamental ter uma inicialização rápida (ninguém gosta de esperar dezenas de segundos apenas para inserir um destino de navegação).

O Android 8.0 permite tempos de inicialização reduzidos ao oferecer suporte a diversas melhorias em vários componentes. A tabela a seguir resume essas melhorias de desempenho (medidas em dispositivos Google Pixel e Pixel XL).

Componente Melhoria
Carregador de inicialização
  • Economizou 1,6s removendo o log UART
  • Economizou 0,4s mudando para LZ4 do GZIP
Kernel do dispositivo
  • Economizou 0,3s removendo configurações de kernel não utilizadas e reduzindo o tamanho do driver
  • Economizou 0,3s com otimização de pré-busca dm-verity
  • Economizou 0,15s para remover espera/teste desnecessários no driver
  • Economizou 0,12s para remover CONFIG_CC_OPTIMIZE_FOR_SIZE
Ajuste de E/S
  • Salvei 2s na inicialização normal
  • Economizou 25s na primeira inicialização
init.*.rc
  • Economizou 1,5s ao paralelizar comandos init
  • Economizei 0,25s ao iniciar o zigoto mais cedo
  • Economizado 0,22s pelo cpuset tune
Animação de inicialização
  • Iniciado 2s antes na inicialização sem fsck acionado, muito maior na inicialização com inicialização acionada por fsck
  • Economizou 5s no Pixel XL com desligamento imediato da animação de inicialização
Política SELinux Salvo 0,2s por genfscon

Otimizando o Bootloader

Para otimizar o bootloader para melhorar os tempos de inicialização:

  • Para registro:
    • Desative a gravação de log no UART, pois pode levar muito tempo com muitos registros. (Nos dispositivos Google Pixel, descobrimos que o bootloader fica lento em 1,5s).
    • Registre apenas situações de erro e considere armazenar outras informações na memória com um mecanismo separado para recuperação.
  • Para descompactação do kernel, considere usar LZ4 para hardware contemporâneo em vez de GZIP (exemplo patch ). Tenha em mente que diferentes opções de compactação do kernel podem ter diferentes tempos de carregamento e descompactação, e algumas opções podem funcionar melhor que outras para o seu hardware específico.
  • Verifique os tempos de espera desnecessários para entrada no modo especial/debouncing e minimize-os.
  • Passe o tempo de inicialização gasto no bootloader para o kernel como cmdline.
  • Verifique o clock da CPU e considere a paralelização (requer suporte multi-core) para carregamento do kernel e inicialização de E/S.

Otimizando a eficiência de E/S

Melhorar a eficiência de E/S é fundamental para tornar o tempo de inicialização mais rápido, e a leitura de qualquer coisa desnecessária deve ser adiada até após a inicialização (em um Google Pixel, cerca de 1,2 GB de dados são lidos na inicialização).

Ajustando o sistema de arquivos

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

Dispositivos que suportam atualizações contínuas (A/B) se beneficiam muito do ajuste do sistema de arquivos na primeira inicialização (por exemplo, 20s no Google Pixel). Por 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é-busca de hash dm-verity usando a configuração do kernel DM_VERITY_HASH_PREFETCH_MIN_SIZE (o tamanho padrão é 128).
  • Para melhor estabilidade do sistema de arquivos e uma verificação forçada que ocorre em cada inicialização, use a nova ferramenta de geração ext4 definindo TARGET_USES_MKE2FS em BoardConfig.mk.

Analisando E/S

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

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

Para dividir o acesso a 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 a seguir para ajudar na análise do desempenho de inicialização.

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

Otimizando init.*.rc

Init é a ponte entre o kernel até que a estrutura seja estabelecida, e os dispositivos geralmente passam alguns segundos em diferentes estágios de inicialização.

Executando tarefas em paralelo

Embora o init atual do Android seja mais ou menos um processo de thread único, você ainda pode executar algumas tarefas em paralelo.

  • Execute comandos lentos em um serviço de script de shell e junte-se a ele mais tarde, aguardando 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 init exec/wait_for_prop ou qualquer ação que leve muito tempo (no Android 8.0, qualquer comando que leve mais de 50 ms). Por exemplo:
    init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms

    A revisão deste registro pode indicar oportunidades de melhorias.

  • Inicie serviços e habilite dispositivos periféricos em caminhos críticos antecipadamente. Por exemplo, alguns SOCs exigem o início de serviços relacionados à segurança antes de iniciar o SurfaceFlinger. Revise o log do sistema quando o ServiceManager retornar "aguardar serviço" — isso geralmente é um sinal de que um serviço dependente deve ser iniciado primeiro.
  • Remova quaisquer serviços e comandos não utilizados em init.*.rc. Qualquer coisa não usada no estágio inicial de inicialização deve ser adiada para a inicialização ser concluída.

Nota: O serviço de propriedade faz parte do processo init, portanto, chamar setproperty durante a inicialização pode levar a um longo atraso se o init estiver ocupado nos comandos internos.

Usando o ajuste do planejador

Use o ajuste do agendador 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
...

Começando o zigoto cedo

Dispositivos com criptografia baseada em arquivo podem iniciar o zigoto mais cedo no gatilho de início do zigoto (por padrão, o zigoto é iniciado na classe principal, que é muito mais tarde do que o início do zigoto). Ao fazer isso, certifique-se de permitir que o zigoto seja executado em todas as CPUs (pois a configuração incorreta do cpuset pode forçar o zigoto a ser executado em CPUs específicas).

Desativar economia de energia

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

Cuidado: A economia de energia deve ser ativada no modo carregador para maior 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 inicialização não crítica

A inicialização não crítica, como 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}

Otimizando a animação de inicialização

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

Configurando o início antecipado

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

Para fazer com que a bootanimation comece 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, em seguida, inicie os serviços de animação de inicialização e suas dependências (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.

Acabamento limpo

Depois de receber o sinal de saída, o bootanimation desempenha 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 possam efetivamente ocultar quaisquer melhorias feitas. Recomendamos fazer o loop de repetição e o final curtos.

Otimizando SELinux

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

  • Use expressões regulares limpas (regex) . Regex mal formada pode levar a muita sobrecarga ao combinar a política SELinux para sys/devices em file_contexts . Por exemplo, a 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 . Melhorando este regex para /sys/devices/[^/]*abc[^/]*(/.*)? ativará uma correspondência apenas para /sys/devices/abc .
  • Mova os rótulos para genfscon . Este recurso existente do SELinux passa prefixos de correspondência de arquivo para o kernel no binário SELinux, onde o kernel os aplica aos 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 que ocorra a reetiquetagem.

Ferramenta e métodos

Use as ferramentas a seguir para ajudá-lo a coletar dados para metas de otimização.

Gráfico de inicialização

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

Para ativar o gráfico de inicialização:

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

Após a 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 de dados todas as vezes.

Se o bootchart não funcionar e você receber um erro dizendo 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 do pybootchartgui (localizada em ~/Documents/bootchart/pybootchartgui.py )

Systrace

O Systrace permite coletar rastreamentos 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 examinar diretamente o rastreamento do kernel).

Para ativar o systrace durante a inicialização:

  • Em frameworks/native/cmds/atrace/atrace.rc , altere:
      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 habilita o rastreamento (que está desabilitado por padrão).

  • No arquivo device.mk , adicione a seguinte 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 análise detalhada de E/S, adicione também block 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
    
  • Após a inicialização, busque o rastreamento:

    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