Ottimizzare i tempi di avvio

Questo documento fornisce ai partner indicazioni per migliorare i tempi di avvio di dispositivi Android specifici. Il tempo di avvio è un componente importante delle prestazioni del sistema, in quanto gli utenti devono attendere il completamento dell'avvio prima di poter utilizzare il dispositivo. Per i dispositivi come le auto, in cui l'avvio a freddo si verifica più di frequente, è fondamentale avere un tempo di avvio rapido (a nessuno piace attendere decine di secondi solo per inserire una destinazione di navigazione).

Android 8.0 consente di ridurre i tempi di avvio supportando diversi miglioramenti su una serie di componenti. La seguente tabella riassume questi miglioramenti delle prestazioni (misurati su dispositivi Google Pixel e Pixel XL).

Componente Miglioramento
Bootloader
  • Risparmiati 1,6 secondi rimuovendo il log UART
  • Risparmio di 0,4 secondi passando da GZIP a LZ4
Kernel del dispositivo
  • Risparmio di 0,3 secondi grazie alla rimozione delle configurazioni del kernel inutilizzate e alla riduzione delle dimensioni del driver
  • Risparmiati 0,3 secondi con l'ottimizzazione del prefetch dm-verity
  • Risparmio di 0,15 secondi per la rimozione di test/tempi di attesa non necessari nel driver
  • Risparmiato 0,12 secondi per rimuovere CONFIG_CC_OPTIMIZE_FOR_SIZE
Ottimizzazione I/O
  • Risparmiati 2 secondi con l'avvio normale
  • Risparmio di 25 secondi al primo avvio
init.*.rc
  • Risparmio di 1,5 secondi grazie al parallelismo dei comandi init
  • Risparmiati 0,25 secondi avviando zygote in anticipo
  • Risparmiati 0,22 secondi con la regolazione di cpuset
Animazione di avvio
  • Avvio 2 secondi prima all'avvio senza attivazione di fsck, molto più lungo all'avvio con attivazione di fsck
  • Risparmio di 5 secondi su Pixel XL con arresto immediato dell'animazione di avvio
Norme SELinux Risparmiato 0,2 secondi da genfscon

Ottimizza il bootloader

Per ottimizzare il bootloader per migliorare i tempi di avvio:

  • Per il logging:
    • Disattiva la scrittura dei log in UART, in quanto può richiedere molto tempo con un volume elevato di logging. (sui dispositivi Google Pixel abbiamo riscontrato che rallenta il bootloader di 1, 5 secondi).
    • Registra solo le situazioni di errore e valuta la possibilità di memorizzare altre informazioni in memoria con un meccanismo di recupero separato.
  • Per la decompressione del kernel, valuta la possibilità di utilizzare LZ4 per l'hardware contemporaneo anziché GZIP (patch di esempio). Tieni presente che le diverse opzioni di compressione del kernel possono avere tempi di caricamento e decompressione diversi e alcune opzioni potrebbero funzionare meglio di altre per il tuo hardware specifico.
  • Controlla i tempi di attesa non necessari per il debouncing/l'ingresso in modalità speciale e riducili al minimo.
  • Passa il tempo di avvio trascorso nel bootloader al kernel come cmdline.
  • Controlla la frequenza della CPU e valuta la parallelizzazione (richiede il supporto multi-core) per il caricamento del kernel e l'inizializzazione dell'I/O.

Ottimizza l'efficienza I/O

Migliorare l'efficienza I/O è fondamentale per velocizzare il tempo di avvio e la lettura di qualsiasi elemento non necessario deve essere posticipata al termine dell'avvio (su Google Pixel vengono letti circa 1,2 GB di dati all'avvio).

Ottimizza il file system

La lettura anticipata del kernel di Linux viene attivata quando un file viene letto dall'inizio o quando i blocchi vengono letti in sequenza, rendendo necessario ottimizzare i parametri di pianificazione I/O specificamente per l'avvio (che ha una caratterizzazione del carico di lavoro diversa rispetto alle normali app).

I dispositivi che supportano gli aggiornamenti seamless (A/B) traggono grande vantaggio dalla ottimizzazione del filesystem al primo avvio (ad es. 20 secondi su Google Pixel). Ad esempio, abbiamo ottimizzato i seguenti parametri per 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
    ...

Vari

  • Attiva la dimensione del prefetch dell'hash dm-verity utilizzando la configurazione del kernel DM_VERITY_HASH_PREFETCH_MIN_SIZE (la dimensione predefinita è 128).
  • Per una maggiore stabilità del file system e un controllo forzato interrotto che si verifica su ogni avvio, utilizza il nuovo strumento di generazione ext4 impostando TARGET_USES_MKE2FS in BoardConfig.mk.

Analizza l'I/O

Per comprendere le attività di I/O durante l'avvio, utilizza i dati ftrace del kernel (utilizzati anche da systrace):

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

Per suddividere l'accesso ai file per ciascun file, apporta le seguenti modifiche al kernel (solo kernel di sviluppo; da non utilizzare nei kernel di produzione):

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

Utilizza i seguenti script per analizzare il rendimento del boot.

  • system/extras/boottime_tools/bootanalyze/bootanalyze.py Misura il tempo di avvio con un'analisi dettagliata dei passaggi importanti del processo di avvio.
  • system/extras/boottime_tools/io_analysis/check_file_read.py boot_trace Fornisce informazioni di accesso per ogni file.
  • system/extras/boottime_tools/io_analysis/check_io_trace_all.py boot_trace Fornisce un'analisi dettagliata a livello di sistema.

Ottimizza init.*.rc

L'inizializzazione è il ponte dal kernel fino all'impostazione del framework e solitamente i dispositivi impiegano alcuni secondi in diverse fasi di inizializzazione.

Eseguire attività in parallelo

Sebbene l'attuale init di Android sia più o meno un processo a thread singolo, puoi comunque eseguire alcune attività in parallelo.

  • Esegui comandi lenti in un servizio di script shell e uniscili in un secondo momento aspettando una proprietà specifica. Android 8.0 supporta questo caso d'uso con un nuovo comandowait_for_property.
  • Identifica le operazioni lente in init. Il sistema registra il comando init exec/wait_for_prop o qualsiasi azione che richiede molto tempo (in Android 8.0, qualsiasi comando che richiede più di 50 ms). Ad esempio:
    init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms

    L'esame di questo log potrebbe indicare opportunità di miglioramento.

  • Avvia in anticipo i servizi e abilita i dispositivi periferici nel percorso critico. Ad esempio, alcuni SOC richiedono l'avvio di servizi correlati alla sicurezza prima di avviare SurfaceFlinger. Esamina il log di sistema quando ServiceManager restituisce "wait for service" (attendi servizio). In genere, questo è un segno che è necessario avviare prima un servizio dipendente.
  • Rimuovi i servizi e i comandi inutilizzati in init.*.rc. Qualsiasi elemento non utilizzato nell'inizializzazione iniziale deve essere posticipato al completamento dell'avvio.

Nota: il servizio Property fa parte della procedura di inizializzazione, pertanto l'uso di setproperty durante l'avvio può comportare un lungo ritardo se init è occupato nei comandi di sistema.

Utilizzare l'ottimizzazione dell'scheduler

Utilizza l'ottimizzazione dell'organizzatore per l'avvio anticipato. Esempio da 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

Alcuni servizi potrebbero richiedere un aumento della priorità durante l'avvio. Esempio:

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

Avvia zygote in anticipo

I dispositivi con crittografia basata su file possono avviare zygote prima all'attivatore zygote-start (per impostazione predefinita, zygote viene avviato in class main, molto più tardi rispetto a zygote-start). Quando esegui questa operazione, assicurati di consentire l'esecuzione di zygote su tutte le CPU (poiché l'impostazione errata di cpuset potrebbe forzare l'esecuzione di zygote su CPU specifiche).

Disattivare il risparmio energetico

Durante l'avvio del dispositivo, l'impostazione di risparmio energetico per componenti come UFS e/o il governatore della CPU può essere disattivata.

Attenzione: per motivi di efficienza, il risparmio energetico deve essere attivato in modalità caricabatterie.

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

Rimandare l'inizializzazione non critica

L'inizializzazione non critica, come ZRAM, può essere differita a boot_complete.

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

Ottimizzare l'animazione di avvio

Segui i suggerimenti riportati di seguito per ottimizzare l'animazione di avvio.

Configurare l'avvio anticipato

Android 8.0 consente di avviare l'animazione di avvio in anticipo, prima del montaggio della partizione userdata. Tuttavia, anche se utilizzi la nuova suite di strumenti ext4 in Android 8.0, fsck viene comunque attivato periodicamente per motivi di sicurezza, causando un ritardo nell'avvio del servizio bootanimation.

Per avviare bootanimation in anticipo, suddividi il montaggio fstab in due fasi:

  • Nella fase iniziale, monta solo le partizioni (ad esempio system/ e vendor/) che non richiedono controlli di esecuzione, quindi avvia i servizi di animazione all'avvio e le relative dipendenze (ad esempio servicemanager e surfaceflinger).
  • Nella seconda fase, monta le partizioni (ad esempio data/) che richiedono l'esecuzione di controlli.

L'animazione di avvio verrà avviata molto più velocemente (e in tempo costante) indipendentemente da fsck.

Pulizia finale

Dopo aver ricevuto l'indicatore di uscita, l'animazione di avvio riproduce l'ultima parte, la cui durata può rallentare il tempo di avvio. Un sistema che si avvia rapidamente non ha bisogno di animazioni lunghe che potrebbero nascondere efficacemente eventuali miglioramenti apportati. Ti consigliamo di mantenere brevi sia il loop ripetuto sia la parte finale.

Ottimizza SELinux

Segui questi suggerimenti per ottimizzare SELinux in modo da migliorare i tempi di avvio.

  • Utilizza espressioni regolari (regex) chiare. Le espressioni regolari con formattazione non corretta possono comportare un notevole overhead durante la corrispondenza del criterio SELinux per sys/devices in file_contexts. Ad esempio, la regex /sys/devices/.*abc.*(/.*)? forza erroneamente la scansione di tutte /sys/devices le sottodirectory contenenti "abc", attivando le corrispondenze sia per /sys/devices/abc sia per /sys/devices/xyz/abc. Migliorando questa regex in /sys/devices/[^/]*abc[^/]*(/.*)?, verrà attivata una corrispondenza solo per /sys/devices/abc.
  • Sposta le etichette in genfscon. Questa funzionalità SELinux esistente passa i prefissi di corrispondenza dei file al kernel nel file binario SELinux, dove il kernel li applica ai filesystem generati dal kernel. Ciò contribuisce anche a correggere i file creati dal kernel con etichette errate, impedendo condizioni di gara che possono verificarsi tra i processi dello spazio utente che tentano di accedere a questi file prima che venga eseguita la ridesignazione.

Strumenti e metodi

Utilizza i seguenti strumenti per raccogliere i dati per i target di ottimizzazione.

Bootchart

Bootchart fornisce un'analisi dettagliata del carico della CPU e dell'I/O di tutti i processi per l'intero sistema. Non richiede la ricostruzione dell'immagine di sistema e può essere utilizzato come rapido controllo di conformità prima di iniziare a utilizzare systrace.

Per attivare il grafico di avvio:

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

Dopo l'avvio, recupera il grafico di avvio:

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

Al termine, elimina /data/bootchart/enabled per evitare di raccogliere i dati ogni volta.

Se bootchart non funziona e viene visualizzato un messaggio di errore che indica che bootchart.png non esiste, procedi nel seguente modo:
  1. Esegui i seguenti comandi:
          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. Aggiorna $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh in modo che punti alla copia locale di pybootchartgui (in ~/Documents/bootchart/pybootchartgui.py)

Systrace

Systrace consente di raccogliere sia le tracce del kernel che quelle di Android durante l'avvio. La visualizzazione di systrace può essere utile per analizzare un problema specifico durante l'avvio. Tuttavia, per controllare il numero medio o accumulato durante l'intero avvio, è più facile esaminare direttamente la traccia del kernel.

Per attivare systrace durante l'avvio:

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

    Segui le istruzioni per eseguire le operazioni indicate:

      #    write /sys/kernel/debug/tracing/tracing_on 0
      #    write /sys/kernel/tracing/tracing_on 0
  • Viene attivata la tracciatura (disattivata per impostazione predefinita).

  • Nel file device.mk, aggiungi la seguente riga:
    PRODUCT_PROPERTY_OVERRIDES +=    debug.atrace.tags.enableflags=802922
    PRODUCT_PROPERTY_OVERRIDES +=    persist.traced.enable=0
  • Nel file BoardConfig.mk del dispositivo, aggiungi quanto segue:
    BOARD_KERNEL_CMDLINE := ... trace_buf_size=64M trace_event=sched_wakeup,sched_switch,sched_blocked_reason,sched_cpu_hotplug
  • Per un'analisi dettagliata dell'I/O, aggiungi anche block, ext4 e f2fs.

  • Nel file init.rc specifico del dispositivo, aggiungi quanto segue:
    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
  • Dopo l'avvio, recupera la traccia:

    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