Otimize os tempos de inicialização

Este documento fornece orientação aos parceiros para melhorar os tempos de inicialização de aplicativos Dispositivos Android. O tempo de inicialização é um componente importante do desempenho do sistema, pois os usuários precisam aguardar a conclusão da inicialização para usar o dispositivo. Para dispositivos como carros com inicialização a frio que ocorre com mais frequência, tempo é crítico (ninguém gosta de esperar dezenas de segundos só para inserir um destino da navegação).

O Android 8.0 permite tempos de inicialização reduzidos por ser compatível com várias melhorias em uma variedade de componentes. A tabela a seguir resume esses desempenhos Melhorias (conforme medido em um dispositivo Google Pixel e Pixel XL).

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

Otimizar o carregador de inicialização

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

  • Para geração de registros:
    • Desative a gravação de registros no UART, porque isso pode levar muito tempo com muitas geração de registros. Nos dispositivos Google Pixel, descobrimos que ele deixa o carregador de inicialização 1.5s lento.
    • Registrar apenas situações de erro e 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 de patch). Lembre-se de que diferentes opções de compactação de kernel podem ter cargas de descompressão, e algumas opções podem funcionar melhor do que outras para seu hardware específico.
  • Verifique tempos de espera desnecessários para dedução/entrada no modo especial e minimize para resolvê-los com rapidez.
  • Passar o tempo de inicialização gasto no carregador de inicialização para o kernel como cmdline.
  • Verifique o relógio da CPU e considere usar o carregamento em paralelo (requer suporte a vários núcleos) para carregamento do kernel e inicialização de E/S.

Otimize a eficiência de E/S

Melhorar a eficiência de E/S é essencial para agilizar o tempo de inicialização, e a leitura tudo o que não for necessário deve ser adiado 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

O kernel de leitura antecipada do Linux é iniciado quando um arquivo é lido desde o início ou quando os blocos são lidos sequencialmente, tornando necessário ajustar o programador de E/S específicos para a inicialização (que tem uma carga de trabalho diferente do que os apps normais).

Dispositivos compatíveis com atualizações contínuas (A/B) se beneficiam muito do sistema de arquivos Ajuste na primeira inicialização (por exemplo, 20 segundos no Google Pixel). Um exemplo que 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

  • Ativar 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 no toda inicialização, use a nova ferramenta de geração ext4 configurando TARGET_USES_MKE2FS no BoardConfig.mk.

Analisar E/S

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

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

Para detalhar o acesso a cada arquivo, faça as seguintes alterações no kernel (apenas 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 analisar o desempenho da inicialização.

  • system/extras/boottime_tools/bootanalyze/bootanalyze.py Mede o tempo de inicialização com um detalhamento 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 a cada arquivo.
  • system/extras/boottime_tools/io_analysis/check_io_trace_all.py boot_trace Fornece um detalhamento no nível do sistema.

Otimizar o arquivo init.*.rc

Ele é a ponte do kernel até que o framework seja estabelecido. 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, você ainda podem executar algumas tarefas em paralelo.

  • Executar comandos lentos em um serviço de script de shell e mesclar isso depois aguardando uma propriedade específica. O Android 8.0 oferece suporte a esse caso de uso 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 levando mais de 50 ms). Por 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 melhorias.

  • Inicie os serviços e ative os dispositivos periféricos no caminho crítico antecipadamente. Para exemplo, alguns SOCs exigem a inicialização de serviços relacionados à segurança antes de iniciar com o SurfaceFlinger. Revise o registro do sistema quando o ServiceManager retornar "wait for serviço" — isso geralmente é um sinal de que um serviço dependente deve ser iniciado primeiro.
  • Remova todos os serviços e comandos não utilizados de init.*.rc. Qualquer item não usado em o init no estágio inicial deve ser adiado para que a inicialização seja concluída.

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

Usar o ajuste do programador

Usar o ajuste do programador para a 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 zigoto precoce

Dispositivos com criptografia baseada em arquivos podem iniciar o zigoto mais cedo no início do zigoto (por padrão, o zigoto é iniciado na classe main, que é muito mais recente início-zigoto). Ao fazer isso, certifique-se de permitir que o zigoto seja executado em todas as CPUs (como a configuração de cpuset errada pode forçar a execução do zigoto 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 UFS e/ou CPU o governador pode ser desativado.

Cuidado:a economia de energia deve estar ativada em carregador para ter 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 o início antecipado

O Android 8.0 permite iniciar a animação de inicialização antecipadamente, antes de montar dados do usuário partição. No entanto, mesmo ao usar a nova cadeia de ferramentas ext4 no Android 8.0, o fsck ainda é acionada periodicamente por motivos de segurança, causando um atraso no iniciando o serviço bootAnimation.

Para que a "bootAnimation" comece mais cedo, divida a montagem do fstab em duas fases:

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

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

Concluir limpeza

Depois de receber o sinal de saída, a função "bootAnimation" reproduz a última parte, a duração o que pode tornar o tempo de inicialização mais lento. Um sistema com inicialização rápida não precisa de tempo animações que poderiam esconder efetivamente quaisquer melhorias feitas. Recomendamos deixando o loop repetido e o final curtos.

Otimizar o SELinux

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

  • Use expressões regulares limpas (regex). regex mal formado pode gerar muita sobrecarga ao fazer a correspondência da política SELinux para sys/devices em file_contexts. Por exemplo, o regex /sys/devices/.*abc.*(/.*)? força erroneamente a verificação de todos Subdiretórios /sys/devices que contêm "abc", ativando correspondências para /sys/devices/abc e /sys/devices/xyz/abc. Melhorar essa regex para /sys/devices/[^/]*abc[^/]*(/.*)? vai ativar uma correspondência apenas para /sys/devices/abc.
  • Mova rótulos para genfscon. Esse recurso SELinux existente passa prefixos de correspondência de arquivos para o kernel em no binário do SELinux, em que o kernel os aplica aos nós gerados pelo kernel, nos sistemas de arquivos. Isso também ajuda a corrigir arquivos criados pelo kernel mal rotulados, impedindo condições de corrida que podem ocorrer entre processos do espaço do usuário que tentam acessar esses arquivos antes da nova rotulagem.

Ferramentas e métodos

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

Gráfico de inicialização

O gráfico de inicialização fornece detalhamento da carga de CPU e E/S de todos os processos para todo sistema. Ele não exige a recriação da imagem do sistema e pode ser usado de integridade antes de começar a usar o 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 nos dados todas as vezes.

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

Systrace

O Systrace permite a coleta de rastros do kernel e do Android durante a inicialização. A visualização do Systrace pode ajudar na análise de um problema específico durante a inicialização. (No entanto, para verificar o número médio ou acumulado durante o toda a inicialização, é mais fácil analisar diretamente o trace do kernel).

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 está desativado 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 uma análise detalhada de E/S, adicione também block, 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 trace:

    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
    
.