起動時間の最適化

このドキュメントでは、特定の Android デバイスの起動時間を改善するためのパートナー向けガイダンスを提供します。誰であろうとも、デバイスを使うには起動が終わるまで待たなければなりません。したがって、起動にかかる時間もシステム パフォーマンスの重要な要因であると考えられます。車のようにコールドブートが頻繁に発生するデバイスでは、起動時間を短くすることは必須です(目的地の入力だけに数十秒も待つなど、誰もしたくありません)。

Android 8.0 では、一連のコンポーネントでさまざまな改良点をサポートし、起動時間の短縮を可能にしています。次の表は、Google Pixel および Pixel XL デバイスでのパフォーマンスの改良点をまとめたものです。

コンポーネント 改良点
ブートローダー
  • UART ログの削除により 1.6 秒短縮
  • GZIP から LZ4 への変更により 0.4 秒短縮
デバイス カーネル
  • 未使用のカーネル構成の削除とドライバサイズの縮小により 0.3 秒短縮
  • dm-verity のプリフェッチの最適化により 0.3 秒短縮
  • ドライバでの不要な待ち時間とテストの排除により 0.15 秒短縮
  • CONFIG_CC_OPTIMIZE_FOR_SIZE の削除により 0.12 秒短縮
I/O 調整
  • 通常の起動で 2 秒短縮
  • 初回起動で 25 秒短縮
init.*.rc
  • init コマンドの並列化により 1.5 秒短縮
  • zygote の早期開始により 0.25 秒短縮
  • cpuset の調整により 0.22 秒短縮
起動アニメーション
  • fsck をトリガーしない起動で 2 秒早く開始、fsck がトリガーされるともっと時間がかかる
  • 起動アニメーションの即時シャットダウンにより 5 秒短縮(Pixel XL)
SELinux ポリシー genfscon によって 0.2 秒短縮

ブートローダーを最適化する

ブートローダーを最適化して起動時間を短縮するには:

  • 次にロギングに関する例を示します。
    • UART へのログ書き込みは大量のロギングに時間がかかるため、無効にします(Google Pixel デバイスでは、ブートローダーの動作が 1.5 秒遅くなることが確認されています)。
    • ログにはエラー状況のみを書き込み、他の情報はメモリに格納して別のメカニズムで読み出すことを検討してください。
  • カーネルの展開については、GZIP の代わりに最新ハードウェア向けの LZ4 を使用することを検討してください(パッチ例)。カーネルの圧縮方法によって読み込みと展開に要する時間が異なる場合があり、また、特定のハードウェアによって適した圧縮方法が異なる可能性がある点に注意してください。
  • デバウンスまたは特別なモードの入力を待つ時間が不要に長くないかどうか調べて最小限に抑えます。
  • ブートローダーの起動時間を cmdline としてカーネルに渡します。
  • CPU クロックを調べて、カーネルの I/O の読み込みと初期化における並列化を検討してください(マルチコア サポートが必要です)。

I/O 効率を最適化する

起動時間を短縮するには、I/O 効率の改善が欠かせません。重要でないデータの読み込みは起動後まで延期してください(Google Pixel では、起動時におよそ 1.2 GB のデータが読み込まれます)。

ファイル システムを調整する

Linux カーネルが先読みを開始するのは、ファイルの先頭から読み込むときやブロックの順次読み込みを行うときで、そのため起動時の I/O スケジューラ パラメータの調整が必要になります(通常のアプリとはワークロードの特徴が異なります)。

シームレス(A/B)アップデートをサポートしているデバイスでは、ファイルシステムを調整することで初回起動時に大きなメリットがあります(Google Pixel の場合は 20 秒短縮)。たとえば、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_HASH_PREFETCH_MIN_SIZE を使用して、dm-verity でのハッシュのプリフェッチ サイズを有効にします(デフォルト サイズは 128 です)。
  • ファイル システムの安定性を高め、起動のたびに強制チェックが実行されないように、BoardConfig.mk で TARGET_USES_MKE2FS を設定して新しい ext4 生成ツールを使用します。

I/O を分析する

起動時の I/O アクティビティを把握するには、カーネルの 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 の現在の init はほぼシングル スレッド化されたプロセスですが、タスクの一部は並列実行が可能です。

  • シェル スクリプト サービスで低速コマンドを実行し、特定のプロパティを待ってから結合します。Android 8.0 は、新しい wait_for_property コマンドを使用したこのユースケースをサポートしています。
  • init で遅い処理を特定します。init のコマンド exec / wait_for_prop、または長時間かかるアクションがログに記録されます(Android 8.0 では、所要時間が 50 ミリ秒を超えたコマンドが記録されます)。次に例を示します。
    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 の初期段階で使用されないものはすべて、起動が完了するまで遅らせることをおすすめします。

注: プロパティ サービスは init プロセスの一部であるため、起動中に組み込みコマンドで init がビジー状態にあるときに 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
...

zygote を早期に開始する

ファイルベースの暗号化を使用するデバイスでは、zygote-start のトリガー時点で zygote を早期開始できます(デフォルトでは、zygote は zygote-start よりもずっと後の class main で開始されます)。この場合は、必ずすべての CPU で zygote を実行できるようにしてください。cpuset 設定に誤りがあると、特定の CPU で zygote が強制実行される可能性があります。

省電力の無効化

デバイスの起動中は、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 では、ユーザーデータ パーティションをマウントする前に起動アニメーションを早期に開始できます。しかし、Android 8.0 で新しい ext4 ツールチェーンを使用しても、安全上の理由から fsck は変わらず定期的に実行されるため、起動アニメーション サービスの開始に遅れが生じます。

起動アニメーションを早期に開始するには、fstab マウントを次の 2 つのフェーズに分割します。

  • 最初のフェーズでは、実行チェックが不要なパーティション(system/vendor/ など)のみをマウントしてから、起動アニメーション サービスとその依存関係(servicemanager や surfaceflinger など)を開始します。
  • 2 つ目のフェーズでは、実行チェックが必要なパーティション(data/ など)をマウントします。

fsck に関係なく、起動アニメーションがかなり早く一定時間内に開始されるようになります。

きれいに終わらせる

exit シグナルの受信後、起動アニメーションの最後の部分が再生されますが、これが起動時間を長びかせる場合があります。起動が速いシステムには、せっかくの改良点を事実上隠してしまう長時間のアニメーションは不要です。繰り返しループと最後の部分をどちらも短くすることをおすすめします。

SELinux を最適化する

SELinux を最適化して起動時間を短縮するには、次のヒントを参考にしてください。

  • 適切な正規表現を使用します。不適切な形式の正規表現を使用すると、file_contextssys/devices に対して SELinux ポリシーを照合するときに大量のオーバーヘッドが発生する可能性があります。たとえば正規表現 /sys/devices/.*abc.*(/.*)? では、「abc」を含むすべての /sys/devices サブディレクトリのスキャンが誤って強制され、/sys/devices/abc/sys/devices/xyz/abc の両方の一致が可能になります。この場合、正規表現を /sys/devices/[^/]*abc[^/]*(/.*)? に改善すると /sys/devices/abc のみ一致が可能になります。
  • ラベルを genfscon に移行します。 この既存の SELinux 機能はファイル照合の接頭辞を SELinux バイナリのカーネルに渡します。カーネルは自身で生成したファイルシステムにこれを適用します。また、この機能はカーネルが作成するファイルのラベルの誤りを修正し、ラベルが付け変えられる前にこれらのファイルにアクセスしようとするユーザー空間プロセス間に競合状態が発生しないようにします。

ツールと手法

次のツールを使用すると、最適化ターゲットのデータ収集に役立ちます。

bootchart

bootchart は、システム全体での全プロセスに対する CPU および I/O 負荷の詳細を提供します。システム イメージを作り直す必要はなく、systrace に取りかかる前の簡単な健全性テストとして使用できます。

bootchart を有効にするには:

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. pybootchartgui~/Documents/bootchart/pybootchartgui.py にあります)のローカルコピーを指すように $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh を更新します。

Systrace

systrace では、起動時のカーネル トレースと Android トレースの両方を収集できます。 systrace の可視化は、起動時に発生した特定の問題の分析に役立ちます(ただし、起動全体での平均数または累積数を確認するには、カーネル トレースを直接調べるほうが簡単です)。

起動時の 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 分析の場合は、block、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