Tối ưu hoá thời gian khởi động

Tài liệu này cung cấp cho đối tác hướng dẫn để cải thiện thời gian khởi động cho một số ứng dụng Thiết bị Android. Thời gian khởi động là một thành phần quan trọng ảnh hưởng đến hiệu suất của hệ thống người dùng phải đợi quá trình khởi động hoàn tất thì mới sử dụng được thiết bị. Đối với thiết bị chẳng hạn như ô tô khởi động nguội thường xuyên hơn, khởi động nhanh thời gian là vô cùng quan trọng (không ai thích phải chờ hàng chục giây chỉ để nhập một đích điều hướng).

Android 8.0 hỗ trợ một số cải tiến giúp giảm thời gian khởi động trên nhiều thành phần. Bảng sau đây tóm tắt các hiệu suất này mức tăng (được đo trên thiết bị Google Pixel và Pixel XL).

Thành phần Cải thiện
Trình tải khởi động
  • Tiết kiệm 1,6 giây bằng cách xoá nhật ký UART
  • Lưu 0,4 giây bằng cách thay đổi sang LZ4 từ GZIP
Nhân hệ điều hành của thiết bị
  • Tiết kiệm 0,3 giây bằng cách xoá các cấu hình nhân không dùng đến và giảm kích thước trình điều khiển
  • Lưu 0,3 giây nhờ tối ưu hoá tìm nạp trước dm-verity
  • Lưu 0,15 giây để xoá thao tác chờ/kiểm thử không cần thiết trong trình điều khiển
  • Đã lưu 0,12 giây để xoá CONFIG_CC_OPTIMIZE_FOR_SIZE
Dò I/O
  • Đã lưu 2 giây khi khởi động bình thường
  • Tiết kiệm được 25 giây trong lần khởi động đầu tiên
init.*.rc
  • Đã lưu 1,5 giây bằng cách song song các lệnh init
  • Tiết kiệm 0,25 giây bằng cách bắt đầu zygote sớm
  • Lưu 0.22 giây bởi cpuset tune
Ảnh động khởi động
  • Đã bắt đầu khởi động sớm hơn 2 giây mà không kích hoạt fsck, lớn hơn nhiều khi khởi động với fsck đã kích hoạt khởi động
  • Lưu 5 giây trên Pixel XL nhờ tính năng tắt ngay ảnh động khởi động
Chính sách SELinux Lưu 0.2s vào bởi genfscon

Trình tải khởi động Optimize

Cách tối ưu hoá trình tải khởi động để cải thiện thời gian khởi động:

  • Để ghi nhật ký:
    • Tắt tính năng ghi nhật ký vào UART vì có thể mất nhiều thời gian với rất nhiều ghi nhật ký. (Trên các thiết bị Google Pixel, chúng tôi nhận thấy trình tải khởi động làm chậm trình tải khởi động 1,5 giây).
    • Chỉ ghi nhật ký các trường hợp lỗi và cân nhắc việc lưu trữ thông tin khác vào bộ nhớ có cơ chế riêng để truy xuất.
  • Để giải nén hạt nhân, hãy cân nhắc sử dụng LZ4 cho phần cứng hiện đại thay vì GZIP (ví dụ: bản vá). Xin lưu ý rằng các tuỳ chọn nén hạt nhân khác nhau có thể có cách tải và thời gian giải nén và một số lựa chọn có thể hiệu quả hơn các lựa chọn khác cho phần cứng cụ thể.
  • Kiểm tra thời gian chờ không cần thiết để khởi động lại/vào chế độ đặc biệt và giảm thiểu chúng.
  • Truyền thời gian khởi động dành cho trình tải khởi động vào nhân hệ điều hành dưới dạng lệnh cmdline.
  • Kiểm tra xung nhịp CPU và cân nhắc việc song song hoá (yêu cầu hỗ trợ đa lõi) để tải kernel và khởi chạy I/O.

Tối ưu hoá hiệu quả I/O

Việc cải thiện hiệu quả I/O là yếu tố rất quan trọng để làm cho thời gian khởi động nhanh hơn và khả năng đọc mọi thứ không cần thiết đều nên bị trì hoãn cho đến sau khi khởi động (trên Google Pixel, khoảng 1,2 GB dữ liệu được đọc khi khởi động).

Điều chỉnh hệ thống tệp

Nhân hệ điều hành Linux sẽ đọc trước khi tệp được đọc từ đầu hoặc khi các khối được đọc tuần tự, do đó cần phải tinh chỉnh trình lập lịch biểu I/O dành riêng cho việc khởi động (có tải công việc khác nhau) hơn các ứng dụng thông thường).

Những thiết bị hỗ trợ cập nhật (A/B) liền mạch được hưởng lợi rất nhiều từ hệ thống tệp dò trong lần khởi động đầu tiên (ví dụ: 20 giây trên Google Pixel). Ví dụ: chúng tôi đã điều chỉnh các thông số sau cho 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
    ...

Khác

  • Bật kích thước tìm nạp trước hàm băm dm-verity bằng cách sử dụng cấu hình nhân kernel DM_VERITY_HASH_PREPLACE_MIN_SIZE (kích thước mặc định là 128).
  • Để tăng độ ổn định của hệ thống tệp và hoạt động kiểm tra bắt buộc bị bỏ qua diễn ra trên mỗi lần khởi động, hãy sử dụng công cụ tạo ext4 mới bằng cách đặt TARGET_USES_MKE2FS trong BoardConfig.mk.

Phân tích I/O

Để hiểu các hoạt động I/O trong quá trình khởi động, hãy sử dụng dữ liệu ftrace của nhân (cũng được sử dụng bởi systrace):

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

Để phân tích quyền truy cập vào tệp cho từng tệp, hãy thực hiện các thay đổi sau đối với nhân (chỉ hạt nhân phát triển; không sử dụng trong hạt nhân sản xuất):

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

Sử dụng các tập lệnh sau để giúp phân tích hiệu suất khởi động.

  • system/extras/boottime_tools/bootanalyze/bootanalyze.py Đo thời gian khởi động bằng bảng phân tích các bước quan trọng trong quá trình khởi động.
  • system/extras/boottime_tools/io_analysis/check_file_read.py boot_trace cung cấp thông tin truy cập cho mỗi tệp.
  • system/extras/boottime_tools/io_analysis/check_io_trace_all.py boot_trace Cung cấp thông tin chi tiết ở cấp hệ thống.

Tối ưu hoá init.*.rc

Init là cầu nối từ hạt nhân cho đến khi khung được thiết lập và thường dành vài giây trong các giai đoạn khởi tạo khác nhau.

Chạy các tác vụ song song

Mặc dù khởi tạo Android hiện tại ít nhiều là một quy trình theo luồng, nhưng bạn vẫn có thể thực hiện song một số tác vụ.

  • Thực thi các lệnh chậm trong dịch vụ tập lệnh shell và kết hợp dịch vụ đó sau bằng cách đang chờ thuộc tính cụ thể. Android 8.0 hỗ trợ trường hợp sử dụng này bằng một Lệnh wait_for_property.
  • Xác định các thao tác chậm trong khởi tạo. Hệ thống ghi nhật ký lệnh init exec/wait_for_prop hoặc bất kỳ hành động nào mất nhiều thời gian (trong Android 8.0, bất kỳ lệnh nào mất hơn 50 mili giây). Ví dụ:
    init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms

    Việc xem xét nhật ký này có thể cho thấy cơ hội cải thiện.

  • Bắt đầu sớm các dịch vụ và bật các thiết bị ngoại vi ở đường dẫn quan trọng. Cho ví dụ: một số SOC yêu cầu phải bắt đầu các dịch vụ liên quan đến bảo mật trước khi bắt đầu SurfaceFlinger. Xem lại nhật ký hệ thống khi ServiceManager trả về trạng thái "wait for" dịch vụ" — đây thường là dấu hiệu cho thấy cần phải bắt đầu một dịch vụ phụ thuộc đầu tiên.
  • Xoá mọi dịch vụ và lệnh không dùng đến trong init.*.rc. Bất kỳ giá trị nào không được sử dụng trong init giai đoạn đầu phải được trì hoãn để quá trình khởi động hoàn tất.

Lưu ý: Dịch vụ tài sản là một phần của quy trình khởi tạo, vì vậy, việc gọi setproperty trong quá trình khởi động có thể gây ra độ trễ dài nếu khởi động đang bận trong các lệnh được tích hợp sẵn.

Sử dụng tính năng điều chỉnh bộ lập lịch

Sử dụng tính năng điều chỉnh bộ lập lịch biểu để khởi động sớm. Ví dụ từ 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

Một số dịch vụ có thể cần tăng mức độ ưu tiên trong quá trình khởi động. Ví dụ:

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

Bắt đầu hợp tử sớm

Các thiết bị được mã hoá dựa trên tệp có thể khởi động zygote sớm hơn khi zygote bắt đầu trình kích hoạt (theo mặc định, zygote được khởi chạy tại lớp chính, muộn hơn nhiều so với hợp tử-bắt đầu). Khi thực hiện việc này, hãy nhớ cho phép zygote chạy trong tất cả CPU (như chế độ cài đặt cpuset không chính xác có thể buộc zygote chạy trong một số CPU cụ thể).

Tắt chế độ tiết kiệm điện

Trong khi khởi động thiết bị, chế độ cài đặt tiết kiệm điện cho các thành phần như UFS và/hoặc CPU Thống đốc có thể bị vô hiệu hoá.

Thận trọng: Bạn phải bật tính năng tiết kiệm điện trong chế độ sạc để đạt được hiệu quả cao.

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

Trì hoãn quá trình khởi chạy không quan trọng

Quá trình khởi tạo không quan trọng như ZRAM có thể được trì hoãn đến boot_complete.

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

Ảnh động khi khởi động trong Optimize

Hãy sử dụng các mẹo sau để tối ưu hoá ảnh động khởi động.

Thiết lập tính năng bắt đầu sớm

Android 8.0 cho phép bắt đầu ảnh động khởi động sớm, trước khi kết nối dữ liệu người dùng phân vùng. Tuy nhiên, ngay cả khi sử dụng chuỗi công cụ ext4 mới trong Android 8.0, fsck vẫn được kích hoạt định kỳ vì lý do an toàn, gây chậm trễ cho khởi động dịch vụ khởi động ảnh động.

Để ảnh động khởi động bắt đầu sớm, hãy chia liên kết fstab thành hai giai đoạn:

  • Trong giai đoạn đầu, chỉ gắn các phân vùng (chẳng hạn như system/vendor/) không yêu cầu chạy kiểm tra, sau đó bắt đầu dịch vụ ảnh động khởi động và các phần phụ thuộc của dịch vụ này (chẳng hạn như Servicemanager và surfaceflinger).
  • Trong giai đoạn thứ hai, hãy gắn các phân vùng (chẳng hạn như data/) yêu cầu chạy kiểm tra.

Hoạt ảnh khởi động sẽ khởi động nhanh hơn nhiều (và theo thời gian không đổi) bất kể sck.

Hoàn tất bước làm sạch

Sau khi nhận được tín hiệu thoát, hình động khởi động sẽ phát phần cuối cùng, thời lượng có thể làm chậm thời gian khởi động. Một hệ thống giúp khởi động nhanh chóng không cần thời gian dài để ẩn hiệu quả mọi cải tiến đã thực hiện. Bạn nên tạo nên vòng lặp lặp lại và phần kết thúc ngắn gọn.

Tối ưu hoá SELinux

Sử dụng các mẹo sau để tối ưu hoá SELinux nhằm cải thiện thời gian khởi động.

  • Sử dụng biểu thức chính quy (regex) sạch. Biểu thức chính quy có định dạng kém có thể dẫn đến nhiều hao tổn khi phù hợp với chính sách SELinux cho sys/devices trong file_contexts. Ví dụ: biểu thức chính quy /sys/devices/.*abc.*(/.*)? buộc quét nhầm tất cả /sys/devices thư mục con chứa "abc", cho phép so khớp cho cả /sys/devices/abc/sys/devices/xyz/abc. Việc cải thiện biểu thức chính quy này thành /sys/devices/[^/]*abc[^/]*(/.*)? sẽ chỉ bật đối sánh cho /sys/devices/abc.
  • Di chuyển nhãn sang genfscon. Tính năng SELinux hiện có này truyền các tiền tố phù hợp với tệp vào nhân hệ điều hành trong tệp nhị phân SELinux, nơi hạt nhân áp dụng chúng cho nhân hệ điều hành được tạo hệ thống tệp. Việc này cũng giúp sửa các tệp do nhân hệ điều hành bị gắn nhãn sai, tránh có thể xảy ra giữa các quy trình trong không gian người dùng cố gắng truy cập các tệp này trước khi việc gắn nhãn lại diễn ra.

Công cụ và phương thức

Sử dụng các công cụ sau để giúp bạn thu thập dữ liệu cho các mục tiêu tối ưu hoá.

Biểu đồ khởi động

Sơ đồ khởi động cung cấp bảng chi tiết về tải CPU và I/O của tất cả các quy trình cho toàn bộ hệ thống. Công cụ này không yêu cầu xây dựng lại hình ảnh hệ thống và có thể được dùng làm kiểm tra sự an toàn trước khi sử dụng systrace.

Cách bật biểu đồ khởi động:

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

Sau khi khởi động, hãy tìm nạp biểu đồ khởi động:

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

Khi hoàn tất, hãy xoá /data/bootchart/enabled để ngừng thu thập dữ liệu mọi lúc.

Nếu biểu đồ khởi động không hoạt động và bạn gặp lỗi cho biết bootchart.png không tồn tại, hãy như sau:
  1. Chạy các lệnh sau:
          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. Cập nhật $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh để trỏ đến bản sao cục bộ của pybootchartgui (đặt tại ~/Documents/bootchart/pybootchartgui.py)

Systrace

Systrace cho phép thu thập cả dấu vết hạt nhân và dấu vết Android trong quá trình khởi động. Việc trực quan hoá systrace có thể giúp phân tích vấn đề cụ thể trong khởi động. (Tuy nhiên, để kiểm tra số liệu trung bình hoặc số liệu tích luỹ trong toàn bộ quá trình khởi động, sẽ dễ dàng hơn nếu trực tiếp xem dấu vết nhân hệ điều hành).

Cách bật systrace trong khi khởi động:

  • Trong frameworks/native/cmds/atrace/atrace.rc, hãy thay đổi:
      write /sys/kernel/debug/tracing/tracing_on 0
      write /sys/kernel/tracing/tracing_on 0

    Đến:

      #    write /sys/kernel/debug/tracing/tracing_on 0
      #    write /sys/kernel/tracing/tracing_on 0
  • Thao tác này sẽ bật tính năng theo dõi (bị tắt theo mặc định).

  • Trong tệp device.mk, hãy thêm dòng sau:
    PRODUCT_PROPERTY_OVERRIDES +=    debug.atrace.tags.enableflags=802922
    PRODUCT_PROPERTY_OVERRIDES +=    persist.traced.enable=0
  • Trong tệp BoardConfig.mk của thiết bị, hãy thêm đoạn mã sau:
    BOARD_KERNEL_CMDLINE := ... trace_buf_size=64M trace_event=sched_wakeup,sched_switch,sched_blocked_reason,sched_cpu_hotplug
  • Để phân tích I/O chi tiết, hãy thêm khối, ext4 và f2fs.

  • Trong tệp init.rc dành riêng cho thiết bị, hãy thêm đoạn mã sau:
    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
    
  • Sau khi khởi động, hãy tìm nạp dấu vết:

    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