Optymalizacja czasu uruchamiania

Ten dokument zawiera wskazówki dla partnerów dotyczące skracania czasu uruchamiania określonych urządzeń z Androidem. Czas uruchamiania jest ważnym elementem wydajności systemu, ponieważ użytkownicy muszą czekać na zakończenie uruchamiania, zanim będą mogli korzystać z urządzenia. W przypadku urządzeń takich jak samochody, w których przypadku uruchamianie z zimnego stanu jest częstsze, szybki czas uruchamiania jest kluczowy (nikt nie lubi czekać kilkadziesiąt sekund tylko po to, aby podać cel nawigacji).

Android 8.0 umożliwia skrócenie czasu uruchamiania dzięki obsłudze kilku ulepszeń w różnych komponentach. W tabeli poniżej podsumowano te ulepszenia wydajności (zmierzone na urządzeniach Google Pixel i Pixel XL).

Komponent Ulepszenie
Program rozruchowy
  • Zaoszczędzono 1,6 s dzięki usunięciu dziennika UART
  • Zaoszczędzono 0,4 s, ponieważ zamiast GZIP użyto LZ4
Jądro urządzenia
  • Zaoszczędzono 0,3 s dzięki usunięciu nieużywanych konfiguracji jądra i zmniejszeniu rozmiaru sterownika
  • Oszczędzanie 0,3 s dzięki optymalizacji wstępnego pobierania dm-verity
  • Zaoszczędzono 0,15 s dzięki usunięciu zbędnego oczekiwania/testu w sterowniku
  • Zaoszczędzono 0,12 s dzięki usunięciu parametru CONFIG_CC_OPTIMIZE_FOR_SIZE
Dostosowywanie I/O
  • Oszczędność 2 s podczas normalnego uruchamiania
  • Oszczędność 25 s podczas pierwszego uruchamiania
init.*.rc
  • Zaoszczędzono 1,5 s dzięki równoległemu wykonywaniu poleceń inicjujących
  • Zaoszczędzono 0,25 s dzięki wcześniejszemu uruchomieniu zygote
  • Zaoszczędzono 0,22 s dzięki ustawieniu cpuset
Animacja uruchamiania
  • Rozpoczęcie 2 sekund wcześniej podczas uruchamiania bez wywołania fsck, znacznie dłuższy czas uruchamiania po wywołaniu fsck
  • Oszczędzanie 5 s na Pixelu XL dzięki natychmiastowemu wyłączaniu animacji uruchamiania
Zasady SELinux Zaoszczędzono 0,2 s dzięki genfscon

Optymalizacja programu rozruchowego

Aby zoptymalizować bootloader pod kątem skrócenia czasu uruchamiania:

  • Logowanie:
    • Wyłącz zapisywanie logów na porcie UART, ponieważ może to zająć dużo czasu przy dużej ilości logów. (na urządzeniach Google Pixel okazało się, że spowalnia ono bootloader o 1,5 s).
    • Rejestruj tylko sytuacje wystąpienia błędów i rozważ przechowywanie innych informacji w pamięci z użyciem osobnego mechanizmu wyszukiwania.
  • W przypadku dekompresji jądra zastanów się nad użyciem LZ4 na nowoczesnym sprzęcie zamiast GZIP (np. patch). Pamiętaj, że różne opcje kompresji jądra mogą mieć różne czasy wczytywania i dekompresji, a niektóre opcje mogą działać lepiej niż inne w przypadku Twojego konkretnego sprzętu.
  • Sprawdź niepotrzebne czasy oczekiwania na debouncing/special mode entry i zminimalizuj je.
  • Przekazywanie czasu rozruchu spędzonego w programie rozruchowym do jądra jako cmdline.
  • Sprawdź zegar procesora i rozważ użycie równoległości (wymaga obsługi wielu rdzeni) do ładowania jądra i inicjowania operacji wejścia/wyjścia.

Optymalizacja wydajności we/wy

Zwiększanie wydajności we/wy to kluczowy element przyspieszania czasu uruchamiania, a odczytywanie niepotrzebnych danych powinno być odkładane na czas po uruchomieniu (na telefonie Google Pixel podczas uruchamiania odczytywanych jest około 1,2 GB danych).

Dostosowywanie systemu plików

Funkcja odczytu w przód w jądrze Linuksa włącza się, gdy plik jest odczytywany od początku lub gdy bloki są odczytywane sekwencyjnie, co wymaga dostosowania parametrów harmonogramu we/wy w szczególny sposób na potrzeby uruchamiania (które ma inną charakterystykę obciążenia niż zwykłe aplikacje).

Urządzenia obsługujące płynne aktualizacje (A/B) korzystają z dostosowywania systemu plików podczas pierwszego uruchamiania (np. 20 s na Google Pixel). Na przykład w przypadku Google Pixel zoptymalizowaliśmy te parametry:

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
    ...

Różne

  • Włącz rozmiar wyprzedzającego pobierania hasha dm-verity za pomocą konfiguracji jądra (DM_VERITY_HASH_PREFETCH_MIN_SIZE). Domyślny rozmiar to 128.
  • Aby zwiększyć stabilność systemu plików i wyeliminować wymuszone sprawdzanie, które występuje przy każdym uruchomieniu, użyj nowego narzędzia do generowania ext4, ustawiając TARGET_USES_MKE2FS w pliku BoardConfig.mk.

Analiza I/O

Aby poznać aktywność we/wy w trakcie uruchamiania, użyj danych ftrace jądra (używanych też przez systrace):

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

Aby określić dostęp do poszczególnych plików, wprowadź w jądrze te zmiany (tylko w jądrze deweloperskim; nie używaj ich w jądrach produkcyjnych):

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

Aby przeanalizować wydajność uruchamiania, użyj poniższych skryptów.

  • system/extras/boottime_tools/bootanalyze/bootanalyze.py Pomiary czasu uruchamiania z podziałem na ważne etapy procesu uruchamiania.
  • system/extras/boottime_tools/io_analysis/check_file_read.py boot_trace Podaje informacje o dostępie do poszczególnych plików.
  • system/extras/boottime_tools/io_analysis/check_io_trace_all.py boot_trace Pokazuje podział na poziomie systemu.

Optymalizacja pliku init.*.rc

Inicjowanie to proces, który łączy jądro z ramami. Na różnych etapach inicjowania urządzenia zwykle spędzają kilka sekund.

równoległe wykonywanie zadań;

Chociaż bieżący proces inicjalizacji Androida jest mniej więcej jednowątkowy, niektóre zadania można wykonywać równolegle.

  • Wykonuj powolne polecenia w usłudze skryptu powłoki i dołącz do niej później, oczekując na określoną właściwość. Android 8.0 obsługuje ten przypadek użycia za pomocą nowego polecenia wait_for_property.
  • Identyfikowanie wolnych operacji w init. System rejestruje polecenie init exec/wait_for_prop lub każde działanie, które zajmuje dużo czasu (w Androidzie 8.0 każde polecenie, które zajmuje więcej niż 50 ms). Przykład:
    init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms

    Przejrzyj ten dziennik, aby znaleźć możliwości poprawy.

  • Wczesna aktywacja usług i urządzeń peryferyjnych na ścieżce krytycznej. Na przykład niektóre zespoły SOC wymagają uruchomienia usług związanych z bezpieczeństwem przed uruchomieniem narzędzia SurfaceFlinger. Sprawdź dziennik systemowy, gdy ServiceManager zwróci „wait for service” (zaczekaj na usługę) – zwykle oznacza to, że najpierw należy uruchomić usługę zależną.
  • Usuń wszystkie nieużywane usługi i polecenia w pliku init.*.rc. Wszystko, czego nie używa się na wczesnym etapie inicjalizacji, powinno zostać odroczone do momentu zakończenia uruchamiania.

Uwaga: usługa Property jest częścią procesu inicjowania, więc wywołanie funkcji setproperty podczas uruchamiania może spowodować długie opóźnienie, jeśli inicjowanie jest zajęte wykonywaniem wbudowanych poleceń.

Używanie dostosowywania algorytmu szeregowania

Użyj dostrajania harmonogramu do wczesnego uruchamiania. Przykład na telefonie 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

Niektóre usługi mogą wymagać podwyższenia priorytetu podczas uruchamiania. Przykład:

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

Wczesna zygota

Urządzenia z szyfrowaniem na poziomie pliku mogą uruchomić zygote wcześniej, gdy zostanie wywołany przez zygote-start (domyślnie zygote jest uruchamiany w ramach metody main, czyli znacznie później niż zygote-start). Pamiętaj, aby zezwolić na uruchamianie zygote na wszystkich procesorach (nieprawidłowe ustawienie cpuset może spowodować uruchomienie zygote na określonych procesorach).

Wyłączanie oszczędzania energii

Podczas uruchamiania urządzenia ustawienie oszczędzania energii dla komponentów takich jak UFS lub sterownik procesora może być wyłączone.

Ostrzeżenie: w trybie ładowania należy włączyć oszczędzanie energii.

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

Opóźnij niekrytyczne inicjalizację

Niekrytyczna inicjalizacja, np. ZRAM, może zostać odroczona do boot_complete.

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

Optymalizowanie animacji uruchamiania

Aby zoptymalizować animację uruchamiania, skorzystaj z podanych niżej wskazówek.

Konfigurowanie wczesnego rozpoczęcia

Android 8.0 umożliwia wcześniejsze rozpoczęcie animacji uruchamiania, jeszcze przed zamontowaniem partycji danych użytkownika. Jednak nawet przy użyciu nowego łańcucha narzędzi ext4 w Androidzie 8.0, fsck jest nadal okresowo uruchamiany ze względów bezpieczeństwa, co powoduje opóźnienie uruchamiania usługi bootanimation.

Aby animacja uruchamiania się systemu zaczęła się wcześniej, podziel punkt zamontowania fstab na 2 fazy:

  • Na wczesnym etapie zamontuj tylko partycje (np. system/vendor/), które nie wymagają sprawdzania podczas uruchamiania, a następnie uruchom usługi animacji rozruchu i ich zależności (np. servicemanager i surfaceflinger).
  • W drugiej fazie zamontuj partycje (np. data/), które wymagają wykonania kontroli.

Animacja uruchamiania będzie uruchamiana znacznie szybciej (i w ciągłym czasie) niezależnie od chksumy.

Zakończ czyszczenie

Po otrzymaniu sygnału wyjścia animacja uruchamiania odtwarza ostatnią część, której długość może wydłużać czas uruchamiania. System, który szybko się uruchamia, nie potrzebuje długich animacji, które mogłyby skutecznie ukryć wprowadzone ulepszenia. Zalecamy, aby pętla powtarzająca się i finał były krótkie.

Optymalizacja SELinux

Aby zoptymalizować SELinux pod kątem skrócenia czasu uruchamiania, skorzystaj z podanych niżej wskazówek.

  • Używaj prostych wyrażeń regularnych. Nieprawidłowo sformułowany wyrażenie regularne może powodować duże obciążenie podczas dopasowywania zasad SELinux do sys/devices w file_contexts. Na przykład wyrażenie regularne /sys/devices/.*abc.*(/.*)? błędnie wymusza skanowanie wszystkich /sys/devices podkatalogów zawierających „abc”, co umożliwia dopasowanie zarówno do /sys/devices/abc, jak i /sys/devices/xyz/abc. Poprawione wyrażenie regularne /sys/devices/[^/]*abc[^/]*(/.*)? umożliwi dopasowanie tylko do /sys/devices/abc.
  • Przenieś etykiety do genfscon. Ta istniejąca funkcja SELinux przekazuje prefiksy dopasowywania plików do jądra w binarnym SELinux, gdzie jądro stosuje je do generowanych przez siebie systemów plików. Pomaga to również naprawiać błędnie oznaczone pliki utworzone przez jądro, zapobiegając błędom, które mogą wystąpić między procesami w przestrzeni użytkownika próbującymi uzyskać dostęp do tych plików przed ponownym oznaczeniem.

Narzędzia i metody

Korzystaj z tych narzędzi, aby zbierać dane na potrzeby celów optymalizacji.

Bootchart

Bootchart zawiera podział obciążenia procesora i wejścia/wyjścia wszystkich procesów w całym systemie. Nie wymaga odbudowywania obrazu systemu i może służyć jako szybka kontrola sanityzacji przed rozpoczęciem korzystania z systrace.

Aby włączyć bootchart:

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

Po uruchomieniu pobierz wykres uruchamiania:

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

Po zakończeniu usuń /data/bootchart/enabled, aby zapobiec gromadzeniu danych za każdym razem.

Jeśli narzędzie bootchart nie działa i wyświetla błąd informujący, że plik bootchart.png nie istnieje, wykonaj te czynności:
  1. Uruchom te polecenia:
          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. Zaktualizuj plik $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh, aby wskazywał on lokalną kopię pliku pybootchartgui (znajdującego się w folderze ~/Documents/bootchart/pybootchartgui.py).

Systrace

Systrace umożliwia zbieranie podczas uruchamiania zarówno śladów jądra, jak i Androida. Wizualizacja systrace może pomóc w analizie konkretnego problemu podczas uruchamiania. (Jednak aby sprawdzić średnią liczbę lub nagromadzoną liczbę podczas całego uruchamiania, łatwiej jest bezpośrednio przejrzeć ślad jądra).

Aby włączyć systrace podczas uruchamiania:

  • frameworks/native/cmds/atrace/atrace.rc zmień:
      write /sys/kernel/debug/tracing/tracing_on 0
      write /sys/kernel/tracing/tracing_on 0

    Do:

      #    write /sys/kernel/debug/tracing/tracing_on 0
      #    write /sys/kernel/tracing/tracing_on 0
  • Włącza śledzenie (domyślnie jest ono wyłączone).

  • W pliku device.mk dodaj ten wiersz:
    PRODUCT_PROPERTY_OVERRIDES +=    debug.atrace.tags.enableflags=802922
    PRODUCT_PROPERTY_OVERRIDES +=    persist.traced.enable=0
  • W pliku BoardConfig.mk urządzenia dodaj:
    BOARD_KERNEL_CMDLINE := ... trace_buf_size=64M trace_event=sched_wakeup,sched_switch,sched_blocked_reason,sched_cpu_hotplug
  • Aby przeprowadzić szczegółową analizę wejść i wyjść, dodaj też blok i ext4 oraz f2fs.

  • W pliku init.rc dla konkretnego urządzenia dodaj:
    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
  • Po uruchomieniu pobierz ślad:

    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