起動時間の最適化

このドキュメントでは、特定の 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 の読み込みと初期化における並列化を検討してください(マルチコア サポートが必要です)。

カーネルの最適化

起動時間を短縮するには、次のヒントを参考にしてカーネルを最適化します。

デバイスの defconfig の最小化

カーネルの設定を最小限に抑えることでカーネルのサイズを縮小し、読み込みから展開、初期化を高速化して攻撃対象領域を減らせます。デバイスの defconfig を最適化する方法は次のとおりです。

  • 使用していないドライバを特定します/dev ディレクトリと /sys ディレクトリを確認して一般的な SELinux ラベルを持つノードを探します(これらのノードは、ユーザー空間からアクセスできるように設定されていません)。該当するノードが見つかったら削除します。
  • 使用していない CONFIG の設定を解除します。カーネルビルドで生成された .config ファイルを確認し、デフォルトで有効になっている未使用の CONFIG があれば明示的に設定を解除します。たとえば、Google Pixel からは次の CONFIG が未使用として削除されています。
        CONFIG_ANDROID_LOGGER=y
        CONFIG_IMX134=y
        CONFIG_IMX132=y
        CONFIG_OV9724=y
        CONFIG_OV5648=y
        CONFIG_GC0339=y
        CONFIG_OV8825=y
        CONFIG_OV8865=y
        CONFIG_s5k4e1=y
        CONFIG_OV12830=y
        CONFIG_USB_EHCI_HCD=y
        CONFIG_IOMMU_IO_PGTABLE_FAST_SELFTEST=y
        CONFIG_IKCONFIG=y
        CONFIG_RD_BZIP2=y
        CONFIG_RD_LZMA=y
        CONFIG_TI_DRV2667=y
        CONFIG_CHR_DEV_SCH=y
        CONFIG_MMC=y
        CONFIG_MMC_PERF_PROFILING=y
        CONFIG_MMC_CLKGATE=y
        CONFIG_MMC_PARANOID_SD_INIT=y
        CONFIG_MMC_BLOCK_MINORS=32
        CONFIG_MMC_TEST=y
        CONFIG_MMC_SDHCI=y
        CONFIG_MMC_SDHCI_PLTFM=y
        CONFIG_MMC_SDHCI_MSM=y
        CONFIG_MMC_SDHCI_MSM_ICE=y
        CONFIG_MMC_CQ_HCI=y
        CONFIG_MSDOS_FS=y
        # CONFIG_SYSFS_SYSCALL is not set
        CONFIG_EEPROM_AT24=y
        # CONFIG_INPUT_MOUSEDEV_PSAUX is not set
        CONFIG_INPUT_HBTP_INPUT=y
        # CONFIG_VGA_ARB is not set
        CONFIG_USB_MON=y
        CONFIG_USB_STORAGE_DATAFAB=y
        CONFIG_USB_STORAGE_FREECOM=y
        CONFIG_USB_STORAGE_ISD200=y
        CONFIG_USB_STORAGE_USBAT=y
        CONFIG_USB_STORAGE_SDDR09=y
        CONFIG_USB_STORAGE_SDDR55=y
        CONFIG_USB_STORAGE_JUMPSHOT=y
        CONFIG_USB_STORAGE_ALAUDA=y
        CONFIG_USB_STORAGE_KARMA=y
        CONFIG_USB_STORAGE_CYPRESS_ATACB=y
        CONFIG_SW_SYNC_USER=y
        CONFIG_SEEMP_CORE=y
        CONFIG_MSM_SMEM_LOGGING=y
        CONFIG_IOMMU_DEBUG=y
        CONFIG_IOMMU_DEBUG_TRACKING=y
        CONFIG_IOMMU_TESTS=y
        CONFIG_MOBICORE_DRIVER=y
        # CONFIG_DEBUG_PREEMPT is not set
        
  • 起動のたびに不要なテストが実行される原因となる CONFIG を削除します。このような設定(CONFIG_IOMMU_IO_PGTABLE_FAST_SELFTEST)は開発時には便利ですが、実稼働カーネルでは削除してください。

ドライバサイズの最小化

機能が使用されないドライバがデバイス カーネル内にあれば、削除してカーネルサイズを縮小できます。たとえば PCIe 経由で WLAN に接続する場合、SDIO サポートは使用されないため、コンパイル時に削除する必要があります。詳細については、Google Pixel カーネルの net: wireless: cnss: add option to disable SDIO support をご覧ください。

コンパイラのサイズ最適化オプションの削除

カーネル設定の CONFIG_CC_OPTIMIZE_FOR_SIZE を削除します。このフラグは、コードサイズが小さいほどキャッシュ ヒットが高い(したがって速い)ことを前提に導入されましたが、最新のモバイル SoC の高性能化に伴い、この前提はもはや効力はありません。

さらに良いことは、このフラグを削除すると、初期化されていない変数があることをコンパイラが警告できるようになります。Linux カーネルでは、CONFIG_CC_OPTIMIZE_FOR_SIZE フラグが設定されているとこの警告は生成されません(事実、この変更だけで一部の Android デバイス ドライバで数々の重要なバグを発見できました)。

初期化の遅延

起動時に多くのプロセスが開始されますが、起動時間に直接影響するのはクリティカル パス(ブートローダー > カーネル > init > ファイル システム マウント > zygote > システム サーバー)のコンポーネントのみです。カーネルの起動中に initcall をプロファイリングして、動作が遅く init プロセスの開始にとってさほど重要でない周辺デバイスとコンポーネントを特定し、それらを読み込み可能なカーネル モジュールに移動して起動プロセスの後半まで初期化を遅らせます。それらを非同期デバイスまたはドライバ プローブに移動すると、クリティカル パス(カーネル > init)に含まれる動作の遅いコンポーネントの並列初期化に役立ちます。

    BoardConfig-common.mk:
        BOARD_KERNEL_CMDLINE += initcall_debug ignore_loglevel

    driver:
        .probe_type = PROBE_PREFER_ASYNCHRONOUS,
    

注: ドライバの依存関係は、EPROBEDEFER サポートを追加して慎重に解決する必要があります。

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 などの初期化は重要でないため、起動完了まで遅らせることができます。

    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 を削除します。

systrace

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

起動時の systrace を有効にするには:

  • frameworks/native/atrace/atrace.rc の次の箇所を変更します。
    write /sys/kernel/debug/tracing/tracing_on 0

    変更後:

    #write /sys/kernel/debug/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(起動完了時にトレースを停止します)
    • 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
    

注: Chrome では、サイズが大きすぎるファイルは処理できません。tailhead、または grep を使用して boot_trace ファイルの必要な部分を切り出すことを検討してください。また I/O イベントが非常に多いため、I/O 分析はキャプチャした boot_trace を直接分析するより仕方がないことがよくあります。