אופטימיזציה של זמני האתחול

במסמך הזה מפורטות הנחיות לשותפים לשיפור זמני ההפעלה של מכשירי Android זמן האתחול הוא מרכיב חשוב בביצועי המערכת המשתמשים חייבים להמתין לסיום ההפעלה לפני שיוכלו להשתמש במכשיר. למכשירים כמו מכוניות שבהן האתחול במצב התחלתי (cold start) קורה בתדירות גבוהה יותר, לאחר הפעלה מהירה הזמן הוא קריטי (אף אחד לא אוהב להמתין עשרות שניות רק כדי להזין יעד ניווט).

מערכת Android 8.0 מאפשרת לקצר את זמני האתחול על ידי תמיכה בכמה שיפורים במגוון רכיבים. הטבלה הבאה מסכמת את הביצועים האלה שיפורים (כפי שנמדדו במכשירי Google Pixel ו-Pixel XL).

רכיב שיפור
תוכנת אתחול
  • שמרת 1.6 שניות על ידי הסרת יומן UART
  • שמרת 0.4 שניות על ידי שינוי ל-LZ4 מ-GZIP
הליבה של המכשיר
  • חסכת 0.3 שניות באמצעות הסרת הגדרות ליבה (kernel) שלא נמצאות בשימוש והקטנת מנהל ההתקן
  • חיסכון של 0.3 שניות באמצעות אופטימיזציה של שליפה מראש (prefetch) ב-dm-verity
  • שמרת 0.15 שניות כדי להסיר את ההמתנה/הבדיקה המיותרת מהנהג
  • שמרת 0.12 שניות כדי להסיר את CONFIG_CC_OPTIMIZE_FOR_SIZE
כוונון קלט/פלט
  • נשמרו 2 שניות בהפעלה רגילה
  • חיסכון של 25 שניות בהפעלה הראשונה
init.*.rc
  • חיסכון של שנייה וחצי באמצעות פקודות Init מקבילות
  • חסכת 0.25 שניות על ידי התחלת זיגוטה מוקדם
  • נשמר 0.22 שניות לפי כוונון
אנימציית האתחול
  • התחיל 2 שניות מוקדם יותר באתחול בלי fsck מופעל, הרבה יותר גדול בהפעלה עם אתחול fsck מופעל
  • שמרת 5 שניות ב-Pixel XL באמצעות כיבוי מיידי של אנימציית האתחול
מדיניות SELinux חיסכון של 0.2 שניות על ידי genfscon

אופטימיזציה של תוכנת האתחול

כדי לבצע אופטימיזציה של תוכנת האתחול לזמני אתחול משופרים:

  • לרישום ביומן:
    • להשבית את כתיבת היומנים ב-UART כי זה יכול לקחת הרבה זמן עם הרבה רישום ביומן. (במכשירי Google Pixel, גילינו שהיא מאטה את תוכנת האתחול בגרסה 1.5).
    • כדאי לרשום רק מצבי שגיאה ולשקול לשמור פרטים אחרים בזיכרון עם מנגנון נפרד לאחזור.
  • לביטול דחיסה של הליבה, כדאי להשתמש ב-LZ4 לחומרה עכשווית במקום GZIP (לדוגמה patch). חשוב לזכור אפשרויות שונות של דחיסת ליבה עשויות להכיל טעינה שונה לפריסת דחיסה, ואפשרויות מסוימות עשויות לפעול טוב יותר מאחרות לחומרה ספציפית.
  • יש לבדוק זמני המתנה מיותרים כדי לבטל עזיבה מהדף הראשון או להיכנס למצב מיוחד, ולמזער אותם.
  • העברת זמן האתחול בתוכנת האתחול לליבה כ-cmdline.
  • בדיקת שעון המעבד (CPU) ואפשרות להשתמש במקבילות (נדרשת תמיכה עם מספר ליבות) לטעינה של ליבה (kernel) ולאתחול קלט/פלט (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) במהלך האתחול, צריך להשתמש בנתוני ftrace של ליבה (kernel) (גם משמשת systrace):

trace_event=block,ext4 in BOARD_KERNEL_CMDLINE

כדי לקבל פירוט של הגישה לקובץ של כל קובץ, מבצעים את השינויים הבאים בליבה (kernel) (ליבה (kernel) של פיתוח בלבד; אין להשתמש בליבה (kernel) של ייצור):

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 הוא הגשר מהליבה עד להקמת המסגרת, בדרך כלל, מכשירים מבלים כמה שניות בשלבים שונים.

הרצת משימות במקביל

האתחול הנוכחי של Android הוא יותר או פחות תהליך עם שרשור אחד, אבל עדיין יוכל לבצע חלק מהמשימות במקביל.

  • להריץ פקודות איטיות בשירות סקריפט של מעטפת ולהצטרף אליהן מאוחר יותר באמצעות בהמתנה לנכס ספציפי. מערכת Android 8.0 תומכת בתרחיש לדוגמה הזה עם הפקודה wait_for_property.
  • זהו פעולות איטיות באתחול. המערכת רושמת ביומן את פקודת האתחול exec/wait_for_PRO או כל פעולה שנמשכת זמן רב (ב-Android 8.0, כל פקודה שהיא נמשך יותר מ-50 אלפיות השנייה). לדוגמה:
    init: Command 'wait_for_coldboot_done' action=wait_for_coldboot_done returned 0 took 585.012ms

    בדיקת היומן הזה עשויה להצביע על הזדמנויות לשיפור.

  • הפעלת השירותים והפעלת ציוד היקפי בנתיב קריטי. עבור לדוגמה, בחלק מהרשתות החברתיות נדרשת הפעלה של שירותי אבטחה לפני שמתחילים SurfaceFlinger. לבדוק את יומן המערכת כש-ServiceManager מחזיר את ההודעה "wait for" "service" – בדרך כלל זה סימן לכך שצריך להתחיל שירות תלוי קודם.
  • מסירים שירותים ופקודות שלא בשימוש ב-init.*.rc. כל מה שלא נעשה בו שימוש ב- יש לדחות את האתחול עד שהאתחול הושלם.

הערה: שירות הנכס הוא חלק מתהליך ראשוני, לכן setproperty במהלך האתחול יכול לגרום לעיכוב ארוך אם יש עומס באתחול של פקודות מובנות.

שימוש בכוונון של מתזמן

שימוש בכוונון של לוח הזמנים להפעלה מוקדמת. דוגמה מ-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 עדיין מופעלת מדי פעם מטעמי בטיחות, ולכן יש עיכוב בהפעלת שירות האתחול.

כדי שהאתחול יתחיל מוקדם, צריך לפצל את טעינת fstab לשני שלבים:

  • בשלב המוקדם, יש לטעון רק את המחיצות (למשל system/ ו-vendor/) שלא מחייבים הרצה ולאחר מכן להפעיל את שירותי אנימציית האתחול ואת יחסי התלות שלהם (למשל servicemanager ו-screenfling).
  • בשלב השני, התקנת מחיצות (כמו 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. שיפור הביטוי הרגולרי ל-/sys/devices/[^/]*abc[^/]*(/.*)? יוביל להפעיל התאמה רק עבור /sys/devices/abc.
  • מעבירים תוויות אל genfscon. התכונה הקיימת של SELinux מעבירה קידומות להתאמת קבצים אל הליבה ב- בקובץ הבינארי SELinux, שבו הליבה (kernel) מחילה אותן על ליבה (kernel) מערכות קבצים. הפעולה הזו גם עוזרת לתקן קבצים שנוצרו באמצעות ליבה (kernel) שלא מסומנים בתווית, וכך מרוץ תהליכים שעלול להתרחש בין תהליכים במרחב המשתמשים שמנסים לגשת לפני שהתיוג מחדש יתבצע.

כלים ושיטות

אפשר להשתמש בכלים הבאים כדי לאסוף נתונים עבור יעדי אופטימיזציה.

תרשים אתחול

תרשים האתחול מספק פירוט של עומסי המעבד (CPU) והקלט/פלט (I/O) של כל התהליכים כולו המערכת. לא נדרשת בנייה מחדש של תמונת המערכת ואפשר להשתמש בה לבצע בדיקת שגרה לפני שממשיכים למערכת.

כדי להפעיל תרשים אתחול:

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

לאחר האתחול, מאחזרים את תרשים האתחול:

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

בסיום, צריך למחוק את /data/bootchart/enabled כדי למנוע איסוף את הנתונים בכל פעם.

אם תרשים האתחול לא עובד ומופיעה הודעת שגיאה שלפיה 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 מאפשרת לאסוף גם ליבה (kernel) וגם מעקבי 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
  • כדי לבצע ניתוח קלט/פלט מפורט, כדאי להוסיף גם בלוקים, 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