การเพิ่มประสิทธิภาพเวลาบูต

เอกสารนี้ให้คำแนะนำแก่พันธมิตรในการปรับปรุงเวลาบูตสำหรับอุปกรณ์ Android ที่เฉพาะเจาะจง เวลาบูตเป็นองค์ประกอบสำคัญของประสิทธิภาพของระบบ เนื่องจากผู้ใช้ต้องรอให้บูตเสร็จก่อนจึงจะสามารถใช้อุปกรณ์ได้ สำหรับอุปกรณ์เช่นรถยนต์ที่มีการบูตเครื่องขณะเย็นบ่อยกว่า การมีเวลาบูตอย่างรวดเร็วถือเป็นสิ่งสำคัญ (ไม่มีใครชอบการรอหลายสิบวินาทีเพื่อป้อนปลายทางการนำทาง)

Android 8.0 ช่วยลดเวลาในการบูตโดยรองรับการปรับปรุงหลายประการในส่วนประกอบต่างๆ ตารางต่อไปนี้สรุปการปรับปรุงประสิทธิภาพเหล่านี้ (วัดจากอุปกรณ์ Google Pixel และ Pixel XL)

ส่วนประกอบ การปรับปรุง
บูตโหลดเดอร์
  • บันทึก 1.6 วินาทีโดยการลบบันทึก UART
  • บันทึก 0.4 วินาทีโดยเปลี่ยนเป็น LZ4 จาก GZIP
เคอร์เนลของอุปกรณ์
  • บันทึก 0.3 วินาทีโดยการลบการกำหนดค่าเคอร์เนลที่ไม่ได้ใช้และลดขนาดไดรเวอร์
  • บันทึก 0.3 วินาทีด้วยการเพิ่มประสิทธิภาพการดึงข้อมูลล่วงหน้า dm-verity
  • บันทึก 0.15 วินาทีเพื่อลบการรอ/การทดสอบที่ไม่จำเป็นในไดรเวอร์
  • บันทึก 0.12 วินาทีเพื่อลบ CONFIG_CC_OPTIMIZE_FOR_SIZE
การปรับ I/O
  • บันทึก 2 วินาทีในการบูตปกติ
  • ประหยัดเวลา 25 วินาทีในการบูตครั้งแรก
init.*.rc
  • บันทึก 1.5 วินาทีโดยการใช้คำสั่ง init แบบขนาน
  • บันทึก 0.25 วินาทีโดยการเริ่มไซโกตตั้งแต่เนิ่นๆ
  • บันทึก 0.22 วินาทีโดยการปรับแต่ง cpuset
แอนิเมชั่นการบูต
  • เริ่มต้น 2 วินาทีก่อนหน้านี้ในการบู๊ตโดยไม่มีการทริกเกอร์ fsck ซึ่งใหญ่กว่ามากในการบู๊ตด้วยการบูตแบบทริกเกอร์ fsck
  • บันทึก 5 วินาทีบน Pixel XL ด้วยการปิดแอนิเมชั่นการบูตทันที
นโยบายของ SELinux บันทึก 0.2 วินาทีโดย genfscon

การเพิ่มประสิทธิภาพ Bootloader

เพื่อเพิ่มประสิทธิภาพ bootloader เพื่อปรับปรุงเวลาการบูต:

  • สำหรับการบันทึก:
    • ปิดใช้งานการเขียนบันทึกไปยัง UART เนื่องจากอาจใช้เวลานานและมีการบันทึกจำนวนมาก (บนอุปกรณ์ Google Pixel เราพบว่ามันทำให้ bootloader ช้าลง 1.5 วินาที)
    • บันทึกเฉพาะสถานการณ์ข้อผิดพลาด และพิจารณาจัดเก็บข้อมูลอื่นๆ ลงในหน่วยความจำด้วยกลไกที่แยกต่างหากเพื่อดึงข้อมูล
  • สำหรับการบีบอัดเคอร์เนล ให้พิจารณาใช้ LZ4 สำหรับฮาร์ดแวร์ร่วมสมัยแทน GZIP ( แพทช์ ตัวอย่าง) โปรดทราบว่าตัวเลือกการบีบอัดเคอร์เนลที่แตกต่างกันอาจมีเวลาในการโหลดและคลายการบีบอัดที่แตกต่างกัน และบางตัวเลือกอาจทำงานได้ดีกว่าตัวเลือกอื่นๆ สำหรับฮาร์ดแวร์เฉพาะของคุณ
  • ตรวจสอบเวลารอที่ไม่จำเป็นสำหรับการดีเด้ง/เข้าสู่โหมดพิเศษและย่อให้เล็กสุด
  • ส่งผ่านเวลาบูตที่ใช้ใน bootloader ไปยังเคอร์เนลเป็น cmdline
  • ตรวจสอบนาฬิกา CPU และพิจารณาการทำงานแบบขนาน (ต้องรองรับมัลติคอร์) สำหรับการโหลดเคอร์เนลและการเริ่มต้น I/O

การเพิ่มประสิทธิภาพ I/O ให้เหมาะสม

การปรับปรุงประสิทธิภาพ I/O เป็นสิ่งสำคัญในการทำให้เวลาบูตเร็วขึ้น และการอ่านสิ่งที่ไม่จำเป็นควรเลื่อนออกไปจนกว่าจะทำการบู๊ต (บน Google Pixel ข้อมูลประมาณ 1.2GB จะถูกอ่านขณะบู๊ต)

การปรับแต่งระบบไฟล์

เคอร์เนล Linux จะอ่านล่วงหน้าเมื่อไฟล์ถูกอ่านตั้งแต่เริ่มต้นหรือเมื่อบล็อกถูกอ่านตามลำดับ ทำให้จำเป็นต้องปรับแต่งพารามิเตอร์ตัวกำหนดตารางเวลา I/O สำหรับการบูตโดยเฉพาะ (ซึ่งมีการกำหนดลักษณะของเวิร์กโหลดที่แตกต่างจากแอปพลิเคชันปกติ)

อุปกรณ์ที่รองรับการอัปเดตแบบราบรื่น (A/B) จะได้รับประโยชน์อย่างมากจากการปรับแต่งระบบไฟล์ในการบูตครั้งแรก (เช่น 20 วินาทีบน Google Pixel) ตัวอย่าง เราได้ปรับแต่งพารามิเตอร์ต่อไปนี้สำหรับ 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
    ...

เบ็ดเตล็ด

  • เปิดขนาดการดึงข้อมูลแฮชล่วงหน้า dm-verity โดยใช้การกำหนดค่าเคอร์เนล DM_VERITY_HASH_PREFETCH_MIN_SIZE (ขนาดเริ่มต้นคือ 128)
  • เพื่อความเสถียรของระบบไฟล์ที่ดีขึ้นและการตรวจสอบบังคับที่ลดลงซึ่งเกิดขึ้นในการบู๊ตทุกครั้ง ให้ใช้เครื่องมือการสร้าง ext4 ใหม่โดยตั้งค่า TARGET_USES_MKE2FS ใน BoardConfig.mk

กำลังวิเคราะห์ I/O

เพื่อทำความเข้าใจกิจกรรม I/O ระหว่างการบู๊ต ให้ใช้ข้อมูล kernel ftrace (ใช้โดย systrace ด้วย):

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

หากต้องการแยกย่อยการเข้าถึงไฟล์สำหรับแต่ละไฟล์ ให้ทำการเปลี่ยนแปลงต่อไปนี้กับเคอร์เนล (เคอร์เนลการพัฒนาเท่านั้น ห้ามใช้ในเคอร์เนลที่ใช้งานจริง):

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

ใช้สคริปต์ต่อไปนี้เพื่อช่วยในการวิเคราะห์ประสิทธิภาพการบูต

  • system/extras/boottime_tools/bootanalyze/bootanalyze.py วัดเวลาบูตพร้อมรายละเอียดขั้นตอนสำคัญในกระบวนการบู๊ต
  • system/extras/boottime_tools/io_analysis/check_file_read.py boot_trace ให้ข้อมูลการเข้าถึงสำหรับแต่ละไฟล์
  • system/extras/boottime_tools/io_analysis/check_io_trace_all.py boot_trace ให้รายละเอียดระดับระบบ

การเพิ่มประสิทธิภาพ init.*.rc

Init เป็นสะพานเชื่อมจากเคอร์เนลจนถึงเฟรมเวิร์กที่ถูกสร้างขึ้น และอุปกรณ์มักจะใช้เวลาสองสามวินาทีในระยะเริ่มต้นที่แตกต่างกัน

ดำเนินภารกิจไปพร้อมๆ กัน

แม้ว่า init ของ Android ในปัจจุบันจะเป็นกระบวนการแบบเธรดเดียวไม่มากก็น้อย แต่คุณยังคงสามารถทำงานบางอย่างควบคู่กันไปได้

  • ดำเนินการคำสั่งที่ช้าในบริการเชลล์สคริปต์และเข้าร่วมในภายหลังโดยรอคุณสมบัติเฉพาะ Android 8.0 รองรับกรณีการใช้งานนี้ด้วยคำสั่ง wait_for_property ใหม่
  • ระบุการดำเนินการที่ช้าใน init ระบบจะบันทึกคำสั่ง init exec/wait_for_prop หรือการดำเนินการใดๆ ที่ใช้เวลานาน (ใน Android 8.0 คำสั่งใดๆ ที่ใช้เวลานานกว่า 50 ms) ตัวอย่างเช่น:
    init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms

    การตรวจสอบบันทึกนี้อาจบ่งบอกถึงโอกาสในการปรับปรุง

  • เริ่มบริการและเปิดใช้งานอุปกรณ์ต่อพ่วงในเส้นทางวิกฤติตั้งแต่เนิ่นๆ ตัวอย่างเช่น SOC บางตัวจำเป็นต้องเริ่มบริการที่เกี่ยวข้องกับความปลอดภัยก่อนที่จะเริ่ม SurfaceFlinger ตรวจสอบบันทึกของระบบเมื่อ ServiceManager ส่งคืน "wait for service" ซึ่งโดยปกติจะเป็นสัญญาณบ่งชี้ว่าบริการที่ต้องพึ่งพาต้องเริ่มต้นก่อน
  • ลบบริการและคำสั่งที่ไม่ได้ใช้ใน init.*.rc สิ่งใดก็ตามที่ไม่ได้ใช้ในช่วงเริ่มต้นควรถูกเลื่อนออกไปเพื่อให้การบูตเสร็จสมบูรณ์

หมายเหตุ: บริการคุณสมบัติเป็นส่วนหนึ่งของกระบวนการ init ดังนั้นการเรียก setproperty ระหว่างการบู๊ตอาจทำให้เกิดความล่าช้าเป็นเวลานานหาก init ไม่ว่างในคำสั่งในตัว

การใช้การปรับกำหนดการ

ใช้การปรับแต่งตัวกำหนดเวลาสำหรับการบูตก่อนเวลา ตัวอย่างจาก 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

บริการบางอย่างอาจต้องมีการเพิ่มลำดับความสำคัญระหว่างการบู๊ต ตัวอย่าง:

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

การเริ่มไซโกตตั้งแต่เนิ่นๆ

อุปกรณ์ที่มีการเข้ารหัสแบบไฟล์สามารถเริ่มไซโกตได้เร็วกว่าที่ทริกเกอร์ไซโกต-สตาร์ท (โดยค่าเริ่มต้น ไซโกตจะถูกเปิดใช้งานที่คลาสหลัก ซึ่งช้ากว่าไซโกเท-สตาร์ทมาก) เมื่อทำเช่นนี้ ตรวจสอบให้แน่ใจว่าได้อนุญาตให้ไซโกตทำงานใน CPU ทั้งหมด (เนื่องจากการตั้งค่า cpuset ที่ไม่ถูกต้องอาจบังคับให้ไซโกตทำงานใน CPU ที่ระบุ)

ปิดการใช้งานการประหยัดพลังงาน

ในระหว่างการบูทอุปกรณ์ การตั้งค่าการประหยัดพลังงานสำหรับส่วนประกอบต่างๆ เช่น UFS และ/หรือตัวควบคุม CPU สามารถปิดใช้งานได้

ข้อควรระวัง: ควรเปิดใช้งานการประหยัดพลังงานในโหมดเครื่องชาร์จเพื่อประสิทธิภาพ

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

เลื่อนการเริ่มต้นที่ไม่สำคัญ

การเริ่มต้นที่ไม่สำคัญ เช่น ZRAM สามารถเลื่อนออกไปเป็น boot_complete ได้

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

การเพิ่มประสิทธิภาพแอนิเมชั่นการบูต

ใช้เคล็ดลับต่อไปนี้เพื่อปรับภาพเคลื่อนไหวขณะบูตให้เหมาะสม

การกำหนดค่าการเริ่มต้นตั้งแต่เนิ่นๆ

Android 8.0 เปิดใช้งานแอนิเมชั่นการบูตก่อนกำหนดก่อนที่จะติดตั้งพาร์ติชันข้อมูลผู้ใช้ อย่างไรก็ตาม แม้ว่าจะใช้กลุ่มเครื่องมือ ext4 ใหม่ใน Android 8.0 fsck ยังคงถูกทริกเกอร์เป็นระยะเนื่องจากเหตุผลด้านความปลอดภัย ทำให้เกิดความล่าช้าในการเริ่มบริการ bootanimation

หากต้องการให้ bootanimation เริ่มต้นเร็ว ให้แบ่ง fstab mount ออกเป็นสองเฟส:

  • ในช่วงเริ่มต้น ให้เมาต์เฉพาะพาร์ติชัน (เช่น system/ และ vendor/ ) ที่ไม่ต้องการการตรวจสอบการรัน จากนั้นเริ่มบริการแอนิเมชั่นสำหรับบูตและการขึ้นต่อกัน (เช่น servicemanager และ surfaceflinger)
  • ในระยะที่สอง ติดตั้งพาร์ติชัน (เช่น data/ ) ที่ต้องมีการตรวจสอบการรัน

แอนิเมชั่นการบูตจะเริ่มเร็วขึ้นมาก (และในเวลาคงที่) โดยไม่คำนึงถึง fsck

จบสะอาด

หลังจากรับสัญญาณทางออกแล้ว ภาพเคลื่อนไหวการบูตจะเล่นส่วนสุดท้าย ซึ่งอาจทำให้เวลาบูตช้าลงได้ ระบบที่บู๊ตได้เร็วไม่จำเป็นต้องมีแอนิเมชั่นยาวๆ ซึ่งสามารถซ่อนการปรับปรุงใดๆ ที่เกิดขึ้นได้อย่างมีประสิทธิภาพ เราขอแนะนำให้ทำทั้งการวนซ้ำและตอนจบให้สั้น

การเพิ่มประสิทธิภาพ SELinux

ใช้เคล็ดลับต่อไปนี้เพื่อเพิ่มประสิทธิภาพ SELinux เพื่อปรับปรุงเวลาบูตให้ดีขึ้น

  • ใช้นิพจน์ทั่วไปที่สะอาด (regex) regex ที่มีรูปแบบไม่ดีอาจทำให้เกิดค่าใช้จ่ายจำนวนมากเมื่อจับคู่นโยบาย SELinux สำหรับ sys/devices ใน file_contexts ตัวอย่างเช่น regex /sys/devices/.*abc.*(/.*)? บังคับให้สแกนไดเรกทอรีย่อย /sys/devices ทั้งหมดที่มี "abc" โดยไม่ได้ตั้งใจ ทำให้สามารถจับคู่ทั้ง /sys/devices/abc และ /sys/devices/xyz/abc ได้ ปรับปรุง regex นี้เป็น /sys/devices/[^/]*abc[^/]*(/.*)? จะเปิดใช้งานการจับคู่สำหรับ /sys/devices/abc เท่านั้น
  • ย้ายป้ายกำกับไปที่ genfscon คุณสมบัติ SELinux ที่มีอยู่นี้ส่งผ่านคำนำหน้าการจับคู่ไฟล์ไปยังเคอร์เนลในไบนารี SELinux โดยที่เคอร์เนลนำไปใช้กับระบบไฟล์ที่สร้างโดยเคอร์เนล นอกจากนี้ยังช่วยแก้ไขไฟล์ที่สร้างเคอร์เนลที่มีป้ายกำกับไม่ถูกต้อง ป้องกันสภาวะการแข่งขันที่อาจเกิดขึ้นระหว่างกระบวนการ userspace ที่พยายามเข้าถึงไฟล์เหล่านี้ก่อนที่จะเกิดการติดป้ายกำกับใหม่

เครื่องมือและวิธีการ

ใช้เครื่องมือต่อไปนี้เพื่อช่วยคุณรวบรวมข้อมูลสำหรับเป้าหมายการเพิ่มประสิทธิภาพ

แผนภูมิบูต

Bootchart ให้รายละเอียดโหลด CPU และ I/O ของกระบวนการทั้งหมดสำหรับทั้งระบบ ไม่จำเป็นต้องสร้างอิมเมจระบบขึ้นใหม่ และสามารถใช้เป็นการตรวจสอบสุขภาพอย่างรวดเร็วก่อนที่จะเข้าสู่ระบบซิสเต็มเทรซ

หากต้องการเปิดใช้งาน Bootchart:

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

หลังจากบูตเครื่องแล้ว ให้ดึงข้อมูลแผนภูมิการบูต:

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

เมื่อเสร็จแล้ว ให้ลบ /data/bootchart/enabled เพื่อป้องกันการรวบรวมข้อมูลทุกครั้ง

หาก Bootchart ใช้งานไม่ได้และคุณได้รับข้อผิดพลาดแจ้งว่าไม่มี bootchart.png ให้ดำเนินการดังต่อไปนี้:
  1. รันคำสั่งต่อไปนี้:
          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. อัปเดต $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh ให้ชี้ไปที่สำเนาในเครื่องของ pybootchartgui (อยู่ที่ ~/Documents/bootchart/pybootchartgui.py )

ซิสเทรซ

Systrace อนุญาตให้รวบรวมทั้งการติดตามเคอร์เนลและ Android ระหว่างการบู๊ตเครื่อง การแสดงภาพระบบสามารถช่วยในการวิเคราะห์ปัญหาเฉพาะระหว่างการบู๊ตเครื่อง (อย่างไรก็ตาม หากต้องการตรวจสอบจำนวนเฉลี่ยหรือจำนวนสะสมระหว่างการบู๊ตทั้งหมด จะง่ายกว่าในการดูการติดตามเคอร์เนลโดยตรง)

หากต้องการเปิดใช้งาน systrace ระหว่างการบู๊ตเครื่อง:

  • ใน frameworks/native/cmds/atrace/atrace.rc ให้เปลี่ยน:
      write /sys/kernel/debug/tracing/tracing_on 0
      write /sys/kernel/tracing/tracing_on 0

    ถึง:

      #    write /sys/kernel/debug/tracing/tracing_on 0
      #    write /sys/kernel/tracing/tracing_on 0
  • สิ่งนี้จะเปิดใช้งานการติดตาม (ซึ่งถูกปิดใช้งานโดยค่าเริ่มต้น)

  • ในไฟล์ device.mk ให้เพิ่มบรรทัดต่อไปนี้:
    PRODUCT_PROPERTY_OVERRIDES +=    debug.atrace.tags.enableflags=802922
    PRODUCT_PROPERTY_OVERRIDES +=    persist.traced.enable=0
  • ในไฟล์ BoardConfig.mk ของอุปกรณ์ ให้เพิ่มข้อมูลต่อไปนี้:
    BOARD_KERNEL_CMDLINE := ... trace_buf_size=64M trace_event=sched_wakeup,sched_switch,sched_blocked_reason,sched_cpu_hotplug
  • สำหรับการวิเคราะห์ I/O โดยละเอียด ให้เพิ่มบล็อกและ ext4 และ f2fs ด้วย

  • ในไฟล์ init.rc เฉพาะอุปกรณ์ ให้เพิ่มข้อมูลต่อไปนี้:
    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
    
  • หลังจากบู๊ตเครื่อง ให้ดึงข้อมูลการติดตาม:

    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