このドキュメントでは、特定の Android デバイスの起動時間を改善するためのパートナー向けガイダンスを提供します。誰であろうとも、デバイスを使うには起動が終わるまで待たなければなりません。したがって、起動にかかる時間もシステム パフォーマンスの重要な要因であると考えられます。車のようにコールドブートが頻繁に発生するデバイスでは、起動時間を短くすることは必須です(目的地の入力だけに数十秒も待つなど、誰もしたくありません)。
Android 8.0 では、一連のコンポーネントでさまざまな改良点をサポートし、起動時間の短縮を可能にしています。次の表は、Google Pixel および Pixel XL デバイスでのパフォーマンスの改良点をまとめたものです。
コンポーネント | 改良点 |
---|---|
ブートローダー |
|
デバイス カーネル |
|
I/O 調整 |
|
init.*.rc |
|
起動アニメーション |
|
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_contexts
のsys/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
が存在しないというエラーが表示された場合は、次の手順を行います。
- 次のコマンドを実行します。
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
-
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
To:
# 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
- デバイス固有の
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
トレースが有効になります(デフォルトでは無効になっています)。
詳細な I/O 分析の場合は、block、ext4、f2fs も追加します。