ベースライン プロファイルを使用してアプリのパフォーマンスを改善する

1. 始める前に

この Codelab では、ベースライン プロファイルを生成してアプリのパフォーマンスを最適化する方法と、ベースライン プロファイルの使用がもたらすパフォーマンス上のメリットを検証する方法を説明します。

必要なもの

演習内容

  • ベースライン プロファイル ジェネレータを使用するようにプロジェクトを設定する。
  • アプリの起動とスクロールのパフォーマンスを最適化するためにベースライン プロファイルを生成する。
  • Jetpack Macrobenchmark ライブラリを使用してパフォーマンスの向上を確認する。

学習内容

  • ベースライン プロファイルと、それを使用してアプリのパフォーマンスを改善する方法
  • ベースライン プロファイルの生成方法
  • ベースライン プロファイルによるパフォーマンス向上

2. 設定方法

最初に、コマンドラインで次のコマンドを使用して、GitHub リポジトリのクローンを作成します。

$ git clone https://github.com/android/codelab-android-performance.git

または、次の 2 つの zip ファイルをダウンロードします。

Android Studio でプロジェクトを開く

  1. [Welcome to Android Studio] ウィンドウで、61d0a4432ef6d396.png [Open an Existing Project] を選択します。
  2. [Download Location]/codelab-android-performance/baseline-profiles フォルダを選択します。baseline-profiles ディレクトリを選択してください。
  3. Android Studio にプロジェクトがインポートされたら、app モジュールを実行して、後で作業に使用するサンプルアプリをビルドできることを確認します。

サンプルアプリ

この Codelab では、JetSnack というサンプルアプリを使用します。これは、Jetpack Compose を使用するオンライン スナック注文アプリです。

アプリのパフォーマンスを測定するには、ベンチマークから UI 要素にアクセスできるように、UI の構造とアプリの動作を知る必要があります。アプリを実行し、スナックを注文して、基本的な画面に慣れてください。アプリの設計構造を詳細に知る必要はありません。

23633b02ac7ce1bc.png

3. ベースライン プロファイルとは

ベースライン プロファイルにより、含まれるコードパスの解釈とジャストインタイム(JIT)コンパイルの手順を行う必要がなくなるため、初回起動からのコード実行速度が約 30% 向上します。アプリまたはライブラリでベースライン プロファイルを配布することで、Android ランタイム(ART)は、事前(AOT)コンパイルによって含まれるコードパスを最適化し、すべての新規ユーザー、すべてのアプリ更新でパフォーマンスを向上できます。このプロファイルに基づく最適化(PGO)を使用すると、起動の最適化、インタラクション ジャンクの削減、エンドユーザーの全体的なランタイム パフォーマンスの向上がアプリの初回起動時から可能になります。

ベースライン プロファイルを使用すると、ユーザーの操作(アプリの起動、画面間の移動、コンテンツのスクロールなど)がすべて、初回の実行時からスムーズに行えるようになります。アプリの速度と応答性を高めると、1 日あたりのアクティブ ユーザー数が増加し、平均リピーター率が高まります。

ベースライン プロファイルは、アプリの最初の起動からアプリのランタイムを改善する一般的なユーザー インタラクションを提供して、アプリの起動以外も含めて最適化する際の指針となります。指針に沿った AOT コンパイルは、ユーザーのデバイスに依存せず、モバイル デバイスではなく開発マシンでリリースごとに 1 回実行できます。ベースライン プロファイルでリリースを送信することで、Cloud プロファイルのみを使用する場合よりもはるかに早くアプリ最適化を利用できるようになります。

ベースライン プロファイルを使用しない場合、すべてのアプリコードは、解釈後にメモリ内で JIT コンパイルされるか、デバイスがアイドル状態のときにバックグラウンドで odex ファイルに JIT コンパイルされます。そのため、アプリを初めてインストールまたは更新してから、新しいパスが最適化されるまでの間は、アプリを実行する際のユーザー エクスペリエンスは最適とはなりません。

4. ベースライン プロファイル ジェネレータ モジュールをセットアップする

ベースライン プロファイルは、新しい Gradle モジュールをプロジェクトに追加する必要のあるインストルメンテーション テスト クラスで生成できます。これをプロジェクトに追加する最も簡単な方法は、Android Studio Hedgehog 以降に付属する Android Studio モジュール ウィザードを使用することです。

[Project] パネルでプロジェクトまたはモジュールを右クリックして、新規モジュール ウィザードのウィンドウを開き、[New] > [Module] を選択します。

232b04efef485e9c.png

開いたウィンドウから、[Templates] ペインの [Baseline Profile Generator] を選択します。

b191fe07969e8c26.png

モジュール名、パッケージ名、言語、ビルド構成言語などの通常のパラメータ以外に、[Target application] と [Use Gradle Managed Device] という新しいモジュール用の通常はない入力が 2 つあります。

[Target application] は、ベースライン プロファイルの生成対象に使用されるアプリ モジュールです。プロジェクトに複数のアプリ モジュールがある場合は、ジェネレータの実行対象とするものを選択します。

[Use Gradle Managed Device] チェックボックスをオンにすると、そのモジュールはベースライン プロファイル ジェネレータが自動的に管理される Android Emulator で実行されるように設定されます。Gradle で管理されているデバイスの詳細については、Gradle で管理されているデバイスを使用したテストのスケーリングをご覧ください。これをオフにすると、ジェネレータでは接続されているデバイスが使用されます。

新しいモジュールに関する詳細をすべて定義したら、[Finish] をクリックして、モジュール作成に進みます。

モジュール ウィザードによる変更

モジュール ウィザードにより、以下のようないくつかの変更がプロジェクトに加えられます。

baselineprofile という名前(またはウィザードで選択した名前)の Gradle モジュールが追加されます。

このモジュールは com.android.test プラグインを使用します。このプラグインはそれをアプリに含めないように Gradle に指示するので、テストコード、つまりベンチマークのみが含められます。これは、ベースライン プロファイルの生成を自動化する androidx.baselineprofile プラグインにも適用されます。

また、選択したターゲット アプリ モジュールにも変更が加えられます。具体的には、新たに作成されたモジュール build.gradle(.kts) に、androidx.baselineprofile プラグインの適用、androidx.profileinstaller 依存関係の追加、baselineProfile 依存関係の追加を行います。

plugins {
  id("androidx.baselineprofile")
}

dependencies {
  // ...
  implementation("androidx.profileinstaller:profileinstaller:1.3.0")
  "baselineProfile"(project(mapOf("path" to ":baselineprofile")))
}

androidx.profileinstaller 依存関係を追加することで、次のことを行います。

  • 生成されたベースライン プロファイルのパフォーマンス向上をローカルで検証する。
  • クラウド プロファイルをサポートしていない Android 7(API レベル 24)と Android 8(API レベル 26)でベースライン プロファイルを使用する。
  • Google Play 開発者サービスがインストールされていないデバイスでベースライン プロファイルを使用する。

baselineProfile(project(":baselineprofile")) 依存関係により、生成されたベースライン プロファイルをどのモジュールから取得する必要があるかを Gradle に知らせます。

これでプロジェクトの設定ができたので、ベースライン プロファイル ジェネレータのクラスを記述します。

5. ベースライン プロファイル ジェネレータを作成する

通常は、アプリの一般的なユーザー ジャーニーについてベースライン プロファイルを生成します。

モジュール ウィザードによって、アプリの起動に関するベースライン プロファイルを生成できる次のような基本的なテストクラス BaselineProfileGenerator が作成されます。

@RunWith(AndroidJUnit4::class)
@LargeTest
class BaselineProfileGenerator {

    @get:Rule
    val rule = BaselineProfileRule()

    @Test
    fun generate() {
        rule.collect("com.example.baselineprofiles_codelab") {
            // This block defines the app's critical user journey. This is where you
            // optimize for app startup. You can also navigate and scroll
            // through your most important UI.

            // Start default activity for your app.
            pressHome()
            startActivityAndWait()

            // TODO Write more interactions to optimize advanced journeys of your app.
            // For example:
            // 1. Wait until the content is asynchronously loaded.
            // 2. Scroll the feed content.
            // 3. Navigate to detail screen.

            // Check UiAutomator documentation for more information about how to interact with the app.
            // https://d.android.com/training/testing/other-components/ui-automator
        }
    }
}

このクラスは、BaselineProfileRule テストルールを使用し、プロファイルを生成するためのテストメソッドを 1 つ含んでいます。プロファイルを生成するためのエントリ ポイントは、collect() 関数です。必要なパラメータは次の 2 つのみです。

  • packageName: アプリのパッケージ
  • profileBlock: 最後のラムダ パラメータ

profileBlock ラムダでは、アプリの典型的なユーザー ジャーニーをカバーするインタラクションを指定します。このライブラリにより、profileBlock が何度か実行されて、呼び出されたクラスと関数が収集され、最適化するコードがあるデバイス上でベースライン プロファイルが生成されます。

デフォルトで、作成されたジェネレータ クラスは、デフォルトの Activity を開始する操作を含み、startActivityAndWait() メソッドを使用してアプリの最初のフレームがレンダリングされるまで待ちます。

カスタムのジャーニーでジェネレータを拡張する

生成されたクラスには、アプリの高度なジャーニーを最適化するために追加の操作を記述する TODO がいくつか含まれています。これは、アプリの起動時以外のパフォーマンスを最適化できるようにするために推奨されています。

このサンプルでは、次のようにすることで、このようなジャーニーを特定できます。

  1. アプリを起動する。これは生成されたクラスで部分的にカバーされています。
  2. コンテンツが非同期に読み込まれるまで待つ。
  3. スナックリストをスクロールする。
  4. スナックの詳細に移動する。

ジェネレータを更新して、以下のスニペットに示す、典型的なジャーニーをカバーしている概略の関数を追加します。

// ...
rule.collect("com.example.baselineprofiles_codelab") {
    // This block defines the app's critical user journey. This is where you
    // optimize for app startup. You can also navigate and scroll
    // through your most important UI.

    // Start default activity for your app.
    pressHome()
    startActivityAndWait()

    // TODO Write more interactions to optimize advanced journeys of your app.
    // For example:
    // 1. Wait until the content is asynchronously loaded.
    waitForAsyncContent()
    // 2. Scroll the feed content.
    scrollSnackListJourney()
    // 3. Navigate to detail screen.
    goToSnackDetailJourney()

    // Check UiAutomator documentation for more information about how to interact with the app.
    // https://d.android.com/training/testing/other-components/ui-automator
}
// ...

それでは、前述のジャーニーごとにインタラクションを記述しましょう。これを MacrobenchmarkScope の拡張関数として記述すると、それによって提供されるパラメータと関数にアクセスできます。そのように記述すると、そのインタラクションをベンチマークに再利用して、パフォーマンスの向上を検証できます。

非同期コンテンツを待つ

多くのアプリは、アプリ起動時になんらかの非同期な読み込みが発生します。これは完全表示状態とも呼ばれ、コンテンツが読み込まれてレンダリングされ、ユーザーが操作できるようになったタイミングをシステムに知らせるものです。上記のインタラクションに関して、ジェネレータでこの状態を待ちます(waitForAsyncContent)。

  1. スナックリストのフィードを見つける。
  2. リスト内のアイテムが画面に表示されるまで待つ。
fun MacrobenchmarkScope.waitForAsyncContent() {
   device.wait(Until.hasObject(By.res("snack_list")), 5_000)
   val contentList = device.findObject(By.res("snack_list"))
   // Wait until a snack collection item within the list is rendered.
   contentList.wait(Until.hasObject(By.res("snack_collection")), 5_000)
}

リストをスクロールするジャーニー

スナックリストをスクロールするジャーニー(scrollSnackListJourney)では、次のインタラクションを実行できます。

  1. スナックリストの UI 要素を見つける。
  2. ジェスチャーのマージンをシステム ナビゲーションがトリガーされないように設定する。
  3. リストをスクロールして UI が確定するまで待つ。
fun MacrobenchmarkScope.scrollSnackListJourney() {
   val snackList = device.findObject(By.res("snack_list"))
   // Set gesture margin to avoid triggering gesture navigation.
   snackList.setGestureMargin(device.displayWidth / 5)
   snackList.fling(Direction.DOWN)
   device.waitForIdle()
}

詳細に移動するジャーニー

最後のジャーニー(goToSnackDetailJourney)では、次のインタラクションを実装します。

  1. スナックリストと、操作できるスナック アイテムをすべて見つける。
  2. リストからアイテムを選択する。
  3. アイテムをクリックし、詳細画面が読み込まれるまで待つ(スナックリストが画面に表示されなくなることを利用できます)。
fun MacrobenchmarkScope.goToSnackDetailJourney() {
    val snackList = device.findObject(By.res("snack_list"))
    val snacks = snackList.findObjects(By.res("snack_item"))
    // Select snack from the list based on running iteration.
    val index = (iteration ?: 0) % snacks.size
    snacks[index].click()
    // Wait until the screen is gone = the detail is shown.
    device.wait(Until.gone(By.res("snack_list")), 5_000)
}

ベースライン プロファイル ジェネレータの実行に必要なインタラクションをすべて定義したので、次はそれを実行するデバイスを定義する必要があります。

6. ジェネレータを実行するデバイスを準備する

ベースライン プロファイルを生成するには、Gradle で管理されているデバイスか、Android 13(API 33)以降を搭載しているデバイスを使用することをおすすめします。

Gradle で管理されているデバイスを使用すると、このプロセスを再現可能にして、ベースライン プロファイルの生成を自動化できます。Gradle で管理されているデバイスでは、Android Emulator を手動で起動して破棄することなく、Android Emulator でテストを実行できます。Gradle で管理されているデバイスの詳細については、Gradle で管理されているデバイスを使用したテストのスケーリングをご覧ください。

Gradle で管理されているデバイスを定義するには、以下のスニペットに示すように、:baselineprofile モジュールの build.gradle.kts ファイルにその定義を追加します。

android {
  // ...

  testOptions.managedDevices.devices {
    create<ManagedVirtualDevice>("pixel6Api31") {
        device = "Pixel 6"
        apiLevel = 31
        systemImageSource = "aosp"
    }
  }
}

この例では、Android 11(API レベル 31)を使用します。aosp システム イメージは、root 権限でのアクセスが可能です。

次に、ベースライン プロファイル Gradle プラグインを、Gradle で管理されているデバイスを使用するように設定します。そのために、以下のスニペットに示すように、デバイスの名前を managedDevices プロパティに追加し、useConnectedDevices を無効にします。

android {
  // ...
}

baselineProfile {
   managedDevices += "pixel6Api31"
   useConnectedDevices = false
}

dependencies {
  // ...
}

次は、ベースライン プロファイルを生成します。

7. ベースライン プロファイルを生成する

デバイスの準備ができたので、ベースライン プロファイルを作成できます。ベースライン プロファイル Gradle プラグインにより、ジェネレータ テスト クラスを実行し、生成されたベースライン プロファイルをアプリに適用するプロセスの全体を自動化する Gradle タスクが作成されます。

新規モジュール ウィザードにより、すべての必要なパラメータを指定して Gradle タスクを実行するのが簡単になる実行設定が作成され、ターミナルと Android Studio を切り替えることなく実行できるようになりました。

タスクを実行するには、Generate Baseline Profile 実行構成を選択して [Run] ボタン 599be5a3531f863b.png をクリックします。

6911ecf1307a213f.png

タスクにより、前に定義したエミュレータ イメージが起動されます。BaselineProfileGenerator テストクラスのインタラクションを何回か実行し、エミュレータを破棄してから、出力を Android Studio に与えます。

ジェネレータが正常に完了すると、Gradle プラグインによって、生成された baseline-prof.txtsrc/release/generated/baselineProfile/ フォルダのターゲット アプリ(:app モジュール)に配置されます。

fa0f52de5d2ce5e8.png

(省略可)コマンドラインからジェネレータを実行する

別の方法として、コマンドラインからジェネレータを実行することができます。Gradle で管理されているデバイスによって作成されたタスク(:app:generateBaselineProfile)を利用できます。このコマンドにより、baselineProfile(project(:baselineProfile)) 依存関係で定義されたプロジェクトのテストがすべて実行されます。このモジュールには、あとでパフォーマンス向上を検証するためのベンチマークも含まれているため、これらのテストは、エミュレータでベンチマークを実行できないという警告とともに失敗します。

android
   .testInstrumentationRunnerArguments
   .androidx.benchmark.enabledRules=BaselineProfile

これに対しては、次の計測ランナー引数を指定して、すべてのベースライン プロファイル ジェネレータをフィルタリングすると、すべてのベンチマークが省略されます。

コマンド全体は次のようになります。

./gradlew :app:generateBaselineProfile -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile

アプリをベースライン プロファイルとともに配布する

ベースライン プロファイルが生成され、アプリのソースコードにコピーされたので、通常どおりにアプリの製品版をビルドします。ベースライン プロファイルをユーザーに配布するにあたって、特別なことを行う必要はありません。それらはビルド中に Android Gradle プラグインによって選択され、AAB か APK に追加されます。次に、ビルドを Google Play にアップロードします。

ユーザーがアプリをインストールするか、前のバージョンから更新すると、ベースライン プロファイルのインストールも行われるため、アプリの初回実行時のパフォーマンスが向上します。

次のステップでは、ベースライン プロファイルによってアプリのパフォーマンスがどのくらい向上したかの確認方法を説明します。

8. (省略可)ベースライン プロファイルの生成をカスタマイズする

ベースライン プロファイル Gradle プラグインには、特定のニーズを満たすようにプロファイルを生成する方法をカスタマイズするオプションがあります。ビルド スクリプトの baselineProfile { } 設定ブロックで、その動作を変更できます。

:baselineprofile モジュール内の設定ブロックは、managedDevices を追加して接続されているデバイスを使用する(useConnectedDevices)か Gradle で管理されているデバイスを使用するかを決定できるジェネレータの実行方法に影響を与えます。

:app ターゲット モジュール内の設定ブロックは、プロファイルの保存場所、またはその生成方法を決定します。以下のパラメータが変更可能です。

  • automaticGenerationDuringBuild: 有効にすると、製品版リリースビルドのビルド時にベースライン プロファイルを生成できます。これは、アプリのリリース前に CI でビルドする際に便利です。
  • saveInSrc: 生成されたベースライン プロファイルを src/ フォルダに保存するかどうかを指定します。このファイルには :baselineprofile ビルドフォルダからもアクセスできます。
  • baselineProfileOutputDir: 生成されたベースライン プロファイルの保存場所を指定します。
  • mergeIntoMain: デフォルトで、ベースライン プロファイルはビルド バリアント(プロダクト フレーバーとビルドタイプ)ごとに生成されます。すべてのプロファイルを src/main にまとめる場合は、このフラグを有効にします。
  • filter: どのクラスまたはメソッドを、生成されるベースライン プロファイルに含める、またはそこから除外するかのフィルタリングができます。これは、ライブラリ デベロッパーがライブラリのコードのみを含めたい場合に便利です。

9. 起動のパフォーマンスの向上を検証する

ベースライン プロファイルを生成して、それをアプリに追加したので、アプリのパフォーマンスに対して期待する効果があったどうかを検証しましょう。

新規モジュール ウィザードにより、StartupBenchmarks というベンチマーク クラスが作成されます。このクラスは、アプリの起動時間を計測するベンチマークを含み、それをアプリでベースライン プロファイルを使用した場合と比較します。

このクラスは次のようになっています。

@RunWith(AndroidJUnit4::class)
@LargeTest
class StartupBenchmarks {

    @get:Rule
    val rule = MacrobenchmarkRule()

    @Test
    fun startupCompilationNone() =
        benchmark(CompilationMode.None())

    @Test
    fun startupCompilationBaselineProfiles() =
        benchmark(CompilationMode.Partial(BaselineProfileMode.Require))

    private fun benchmark(compilationMode: CompilationMode) {
        rule.measureRepeated(
            packageName = "com.example.baselineprofiles_codelab",
            metrics = listOf(StartupTimingMetric()),
            compilationMode = compilationMode,
            startupMode = StartupMode.COLD,
            iterations = 10,
            setupBlock = {
                pressHome()
            },
            measureBlock = {
                startActivityAndWait()

                // TODO Add interactions to wait for when your app is fully drawn.
                // The app is fully drawn when Activity.reportFullyDrawn is called.
                // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
                // from the AndroidX Activity library.

                // Check the UiAutomator documentation for more information on how to
                // interact with the app.
                // https://d.android.com/training/testing/other-components/ui-automator
            }
        )
    }
}

このクラスは、アプリのベンチマークを実行し、パフォーマンス指標を収集できる MacrobenchmarkRule を使用します。ベンチマークを書き込むためのエントリ ポイントは、このルールの measureRepeated 関数です。

この関数には、いくつかのパラメータが必要です。

  • packageName: 計測するアプリ
  • metrics: ベンチマーク中に測定する情報のタイプ
  • iterations: ベンチマークを繰り返す回数
  • startupMode: ベンチマーク開始時のアプリの開始方法
  • setupBlock: 計測前に発生させるアプリとのインタラクション
  • measureBlock: ベンチマーク中に計測するアプリのインタラクション

テストクラスには、startupCompilationeNone()startupCompilationBaselineProfiles()(別の compilationModebenchmark() 関数を呼び出します)の 2 つのクラスも含まれています。

CompilationMode

CompilationMode パラメータは、アプリをマシンコードにプリコンパイルする方法を決定します。以下のオプションがあります。

  • DEFAULT: 可能な場合は、ベースライン プロファイルを使用して、アプリを部分的にプリコンパイルします。compilationMode パラメータが適用されない場合は、このオプションが使用されます。
  • None(): アプリのコンパイル状態をリセットします。アプリのプリコンパイルは行いません。アプリの実行中は、引き続き実行時コンパイル(JIT)が有効です。
  • Partial(): ベースライン プロファイルでのアプリのプリコンパイルまたはウォームアップ実行を行うか、両方を行います。
  • Full(): アプリコード全体をプリコンパイルします。Android 6(API 23)以下では、これが唯一のオプションです。

アプリのパフォーマンスの最適化を開始するときは、DEFAULT コンパイル モードを選択できます。Google Play からアプリをインストールするときと似たパフォーマンスが得られるからです。ベースライン プロファイルがもたらすパフォーマンス上のメリットを確認するには、コンパイル モード NonePartial の結果を比較します。

コンテンツを待つようにベンチマークを変更する

ベンチマークは、アプリとのインタラクションを記述することで、ベースライン プロファイル ジェネレータと同じように記述されます。デフォルトで、作成されたベンチマークは、BaselineProfileGenerator と同じように、最初のフレームのレンダリングだけを待つため、非同期コンテンツを待つように改良することをおすすめします。

そのためには、ジェネレータ用に記述する拡張関数を再利用します。ベンチマークは(StartupTimingMetric() を使用して)起動タイミングをキャプチャするため、ここで非同期コンテンツの待ち合わせのみを追加してから、ジェネレータで定義された他のユーザー ジャーニーのための別のベンチマークを記述することをおすすめします。

// ...
measureBlock = {
   startActivityAndWait()

   // The app is fully drawn when Activity.reportFullyDrawn is called.
   // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
   // from the AndroidX Activity library.
   waitForAsyncContent() // <------- Added to wait for async content.

   // Check the UiAutomator documentation for more information on how to
   // interact with the app.
   // https://d.android.com/training/testing/other-components/ui-automator
}

ベンチマークを実行する

インストルメンテーション テストと同じ方法でベンチマークを実行できるようになりました。テストする関数またはクラス全体を実行するには、関数またはクラスの横のガターアイコンをクリックします。

587b04d1a76d1e9d.png

実機が選択されていることを確認してください。Android Emulator でベンチマークを実行すると、実行時に失敗し、誤った結果が生成される可能性があるという警告が表示されます。技術的にはベンチマークをエミュレータで実行することは可能ですが、ホストマシンのパフォーマンスを測定することになります。負荷が高ければベンチマークのパフォーマンスは低下し、負荷が低ければその逆になります。

94e0da86b6f399d5.png

ベンチマークを実行すると、アプリが再ビルドされ、アプリによってベンチマークが実行されます。ベンチマークは、定義された iterations に基づいて、アプリの開始、停止、さらには再インストールを複数回行います。

ベンチマークが完了したら、次のスクリーンショットに示すように、Android Studio の出力で結果を確認できます。

282f90d5f6ff5196.png

スクリーンショットを見ると、CompilationMode に応じてアプリの起動時間が異なることがわかります。次の表に中央値を示します。

timeToInitialDisplay(ミリ秒)

timeToFullDisplay(ミリ秒)

なし

202.2

818.8

BaselineProfiles

193.7

637.9

改善率

4%

28%

timeToFullDisplay のコンパイル モードによる違いは 180 ms です。つまり、ベースライン プロファイルがあるだけで約 28% の改善になっています。CompilationNone の場合、アプリの起動時にほとんどの JIT コンパイルを行う必要があるので、パフォーマンスが低くなります。CompilationBaselineProfiles の場合、ベースライン プロファイル AOT による部分コンパイルにより、ユーザーが最も使用する可能性の高いコードをコンパイルし、重要でないコードはすぐに読み込む必要がなく事前コンパイルされないため、パフォーマンスは高くなります。

10. (省略可)スクロールのパフォーマンスの向上を検証する

前のステップと同様に、スクロールのパフォーマンスを測定して検証できます。最初に、ベンチマーク ルールと、異なるコンパイル モードを使用する 2 つのテストメソッドを含む ScrollBenchmarks テストクラスを作成します。

@LargeTest
@RunWith(AndroidJUnit4::class)
class ScrollBenchmarks {

   @get:Rule
   val rule = MacrobenchmarkRule()

   @Test
   fun scrollCompilationNone() = scroll(CompilationMode.None())

   @Test
   fun scrollCompilationBaselineProfiles() = scroll(CompilationMode.Partial())

   private fun scroll(compilationMode: CompilationMode) {
       // TODO implement
   }
}

scroll メソッド内から、必要なパラメータを指定して measureRepeated 関数を使用します。metrics パラメータには、UI フレームの生成にかかった時間を測定する FrameTimingMetric を使用します。

private fun scroll(compilationMode: CompilationMode) {
   rule.measureRepeated(
       packageName = "com.example.baselineprofiles_codelab",
       metrics = listOf(FrameTimingMetric()),
       compilationMode = compilationMode,
       startupMode = StartupMode.WARM,
       iterations = 10,
       setupBlock = {
           // TODO implement
       },
       measureBlock = {
           // TODO implement
       }
   )
}

今回は、最初のレイアウトとコンテンツのスクロールの間のフレーム時間だけを計測するために、インタラクションを setupBlockmeasureBlock の間でさらに分割する必要があります。そのため、デフォルト画面を開始する関数を setupBlock に入れ、作成済みの拡張関数 waitForAsyncContent()scrollSnackListJourney()measureBlock に入れます。

private fun scroll(compilationMode: CompilationMode) {
   rule.measureRepeated(
       packageName = "com.example.baselineprofiles_codelab",
       metrics = listOf(FrameTimingMetric()),
       compilationMode = compilationMode,
       startupMode = StartupMode.WARM,
       iterations = 10,
       setupBlock = {
           pressHome()
           startActivityAndWait()
       },
       measureBlock = {
           waitForAsyncContent()
           scrollSnackListJourney()
       }
   )
}

ベンチマークの準備ができたら、前のように実行して、次のスクリーンショットに示すような結果を得ることができます。

84aa99247226fc3a.png

FrameTimingMetric は、50 パーセンタイル、90 パーセンタイル、95 パーセンタイル、99 パーセンタイルのフレーム時間をミリ秒単位で出力します(frameDurationCpuMs)。Android 12(API レベル 31)以上では、フレーム時間が上限を超過した時間(frameOverrunMs)も返します。この値は負数になることがあります。負数の場合は、フレームを生成する時間が余ったことを意味します。

結果を見ると、CompilationBaselineProfiles ではフレーム時間が平均で 2 ミリ秒短くなっていることがわかります。これはユーザーが気づくほどの違いではありませんが、他のパーセンタイルでは結果に顕著な差があります。P99 では、差は 43.5 ms で、90 FPS のデバイスではスキップされたフレーム 3 つ分より大きくなります。たとえば、Google Pixel 6 の場合、フレームのレンダリングに要する最大時間は 1,000 ms / 90 FPS = 約 11 ms です。

11. 完了

お疲れさまでした。以上でこの Codelab は無事に終了し、ベースライン プロファイルを使用してアプリのパフォーマンスを改善できました。

参考情報

以下の資料も参考にしてください。

リファレンス ドキュメント