ビルドのプロファイリングを行う

大規模なプロジェクトや、カスタム ビルドロジックを多数実装したプロジェクトの場合、ビルドプロセスを詳細に調べて、ボトルネックを特定する必要があります。 そのためには、Gradle がビルド ライフサイクルの各フェーズと各ビルドタスクを実行するのにかかる時間をプロファイリングします。たとえば、ビルド プロファイルで、Gradle がプロジェクトの構成に時間をかけすぎていることが判明した場合は、カスタムビルド ロジックを構成フェーズから移動する必要があることを示している可能性があります。また、mergeDevDebugResources タスクがビルド時間の大部分を消費している場合は、画像を WebP に変換するか、PNG 自動最適化を無効にする必要があることを示している可能性があります。

Android Studio 4.0 以降を使用している場合、ビルド パフォーマンスの問題を調査するには、Build Analyzer を使用するのが最適です。

また、Android Studio の外部でビルドをプロファイリングするには、次の 2 つの方法があります。

  1. スタンドアロンの gradle-profiler ツール。ビルドを詳細に分析するための堅牢なツールです。

  2. Gradle の --profile オプション。Gradle コマンドラインから利用できる便利なツールです。

スタンドアロンの gradle-profiler ツールの使用

最適なビルド速度を実現するプロジェクト設定を見つけるには、Gradle Profiler を使用する必要があります。これは、Gradle ビルドのプロファイリング情報とベンチマーク情報を収集するためのツールです。Gradle Profiler を使用すると、ビルドシナリオを作成して複数回実行できるため、結果のばらつきを抑え、結果の再現性を確保できます。

ベンチマーク モードは、クリーンビルドと増分ビルドに関する情報を収集するために使用します。プロファイリング モードは、CPU スナップショットなど、実行に関するより詳細な情報を収集するために使用します。

ベンチマーク用のプロジェクト設定構成の一部を以下に示します。

  • プラグインのバージョン
  • Gradle のバージョン
  • JVM 設定(ヒープサイズ、PermGen サイズ、ガベージ コレクションなど)
  • Gradle ワーカーの数(org.gradle.workers.max)
  • パフォーマンスをさらに向上させるためのプラグイン別オプション

開始方法

  • こちらの手順に沿って gradle-profiler をインストールする
  • gradle-profiler --benchmark --project-dir <root-project> :app:assembleDebug を実行する

これにより、--benchmark はプロジェクトを途中で変更せずにタスクを複数回実行するため、完全に最新のビルドのベンチマークが行われます。その結果、profile-out/ ディレクトリの下にビルド時間を示す HTML レポートが生成されます。

この他にも、以下のような状況はベンチマークに役立つと考えられます。

  • ほとんどの作業を行うクラスのメソッド本体でのコードの変更。
  • プロジェクト全体で使用するモジュールにおける API の変更。独自のコードへの変更よりも頻度は低いですが、より大きな影響があり、ベンチマークの測定に便利です。
  • UI 作業での反復処理をシミュレートするレイアウト編集。
  • 翻訳作業をシミュレートする文字列編集。
  • ビルド自体への変更(Android Gradle プラグインの更新、Gradle の更新、buildSrc の独自のビルドコードの編集など)をシミュレートするためのクリーンビルド。

こうしたユースケースのベンチマークを行うには、gradle-profiler の実行に使用し、該当するソースに対して適切な変更を適用するシナリオを作成します。以降のセクションでは、一般的なシナリオについて説明します。

さまざまなメモリ / CPU 設定のプロファイリング

さまざまなメモリ / CPU 設定のベンチマークを行うには、org.gradle.jvmargs の値を変えて複数のシナリオを作成します。たとえば、以下のようなシナリオを作成できます。

# <root-project>/scenarios.txt
clean_build_2gb_4workers {
    tasks = [":app:assembleDebug"]
    gradle-args = ["--max-workers=4"]
    jvm-args = ["-Xmx2048m"]
    cleanup-tasks = ["clean"]
}
clean_build_parallelGC {
    tasks = [":app:assembleDebug"]
    jvm-args = ["-XX:+UseParallelGC"]
    cleanup-tasks = ["clean"]
}

clean_build_G1GC_4gb {
    tasks = [":app:assembleDebug"]
    jvm-args = ["-Xmx4096m", "-XX:+UseG1GC"]
    cleanup-tasks = ["clean"]
}

gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt を実行すると 3 つのシナリオが実行されるので、各設定の :app:assembleDebug の所要時間を比較できます。

さまざまな Gradle プラグイン バージョンのプロファイリング

Gradle プラグインのバージョンの変更がビルド時間に与える影響を確認するには、そのためのベンチマーク シナリオを作成します。この場合、プラグイン バージョンをシナリオから挿入できるように準備する必要があります。以下のようにルートの build.gradle を変更します。

# <root-project>/build.gradle
buildscript {
    def agpVersion = providers.systemProperty("agpVersion").forUseAtConfigurationTime().orNull ?: '4.1.0'

    ext.kotlin = providers.systemProperty('kotlinVersion').forUseAtConfigurationTime().orNull ?: '1.4.0'

    dependencies {
        classpath "com.android.tools.build:gradle:$agpVersion"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin"
    }
}

これで、シナリオ ファイルから Android Gradle プラグインと Kotlin Gradle プラグインのバージョンを指定し、そのシナリオでソースファイルに新しいメソッドを追加するよう指示できます。

# <root-project>/scenarios.txt
non_abi_change_agp4.1.0_kotlin1.4.10 {
    tasks = [":app:assembleDebug"]
    apply-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    System-properties {
      "agpVersion" = "4.1.0"
      "kotlinVersion" = "1.4.10"
}

non_abi_change_agp4.2.0_kotlin1.4.20 {
    tasks = [":app:assembleDebug"]
    apply-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    System-properties {
      "agpVersion" = "4.2.0-alpha16"
      "kotlinVersion" = "1.4.20"
}

増分ビルドのプロファイリング

ビルドの大半は増分ビルドであり、これはプロファイリングするうえで最も重要なシナリオの 1 つとなっています。Gradle Profiler は、増分ビルドのプロファイリングを幅広くサポートしています。メソッド本体の変更、新しいメソッドの追加、レイアウト リソースや文字列リソースの変更によって、ソースファイルに変更を自動的に適用できます。たとえば、次のような増分シナリオを作成できます。

# <root-project>/scenarios.txt
non_abi_change {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to = ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
}

abi_change {
    tasks = [":app:assembleDebug"]
    apply-abi-change-to = ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
}

layout_change {
    tasks = [":app:assembleDebug"]
    apply-android-layout-change-to = "app/src/main/res/your_layout_file.xml"
}
string_resource_change {
    tasks = [":app:assembleDebug"]
    apply-android-resource-value-change-to = "app/src/main/res/values/strings.xml"
}

gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt を実行すると、ベンチマーク データを含む HTML レポートが生成されます。

増分シナリオは、ヒープサイズ、ワーカー数、Gradle のバージョンなどの他の設定と組み合わせることができます。

# <root-project>/scenarios.txt
non_abi_change_4g {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    jvm-args = ["-Xmx4096m"]
}

non_abi_change_4g_8workers {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    jvm-args = ["-Xmx4096m"]
    gradle-args = ["--max-workers=8"]
}

non_abi_change_3g_gradle67 {
    tasks = [":app:assembleDebug"]
    apply-non-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
                              "app/src/main/java/com/example/your_app/your_code_file.kt"]
    jvm-args = ["-Xmx3072m"]
    version = ["6.7"]
}

クリーンビルドのプロファイリング

クリーンビルドのベンチマークを行うには、gradle-profiler の実行に使用するシナリオを作成します。

# <root-project>/scenarios.txt
clean_build {
    tasks = [":app:assembleDebug"]
    cleanup-tasks = ["clean"]
}

このシナリオを実行するには、次のコマンドを使用します。

gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt

Gradle の --profile オプションの使用

Gradle コマンドラインからビルド プロファイルを生成して表示する手順は次のとおりです。

  1. プロジェクトのルートでコマンドライン ターミナルを開きます。

  2. 次のコマンドを入力して、クリーンビルドを行います。ビルドをプロファイリングする際は、プロファイリングするビルドごとにクリーンビルドを実行する必要があります。これは、タスクへの入力(ソースコードなど)が変更されない場合、Gradle がタスクをスキップするためです。そのため、入力に変更がない 2 回目のビルドは、タスクが再実行されないため、常に高速になります。そのため、ビルド間で clean タスクを実行すると、ビルドプロセス全体をプロファイリングできます。

    // On Mac or Linux, run the Gradle wrapper using "./gradlew".
    gradlew clean
    
  3. 次のフラグを使用して、いずれかのプロダクト フレーバー(「dev」フレーバーなど)のデバッグビルドを実行します。

    gradlew --profile --offline --rerun-tasks assemble<var>Flavor</var>Debug
    
    • --profile: プロファイリングを有効にします。

    • --offline: Gradle がオンラインの依存関係を取得しないようにします。これにより、Gradle が依存関係を更新しようとしたときに発生する遅延が、プロファイリング データに影響しないようにします。Gradle が依存関係をダウンロードしてキャッシュに保存していることを確認するために、プロジェクトを一度ビルドしておく必要があります。

    • --rerun-tasks: Gradle ですべてのタスクを強制的に再実行し、タスクの最適化を無視します。

  4. ビルドが完了したら、[Project] ウィンドウを使用して project-root/build/reports/profile/ ディレクトリに移動します(図 1 を参照)。

    図 1. プロファイル レポートの場所を示すプロジェクト ビュー。

  5. profile-timestamp.html ファイルを右クリックし、[Open in Browser] > [Default] を選択します。レポートは、図 2 のようになります。レポートの各タブを調べると、ビルドに関する情報を確認できます。たとえば、[Task Execution] タブには、Gradle が各ビルドタスクの実行に要した時間が表示されます。

    図 2. ブラウザでレポートを表示する。

  6. 省略可: プロジェクトまたはビルド構成に変更を加える前に、ステップ 3 のコマンドを繰り返します。ただし、--rerun-tasks フラグは省略します。Gradle は、入力が変更されていないタスクを再実行しないことで時間を節約しようとします(図 3 に示すように、レポートの [Task Execution] タブで UP-TO-DATE と表示されます)。そのため、実行すべきでないタスクが実行されているかどうかを特定できます。たとえば、:app:processDevUniversalDebugManifest が UP-TO-DATE としてマークされていない場合、ビルド構成がビルドごとにマニフェストを動的に更新している可能性があります。ただし、:app:checkDevDebugManifest など、一部のタスクはビルドごとに実行する必要があります。

    図 3. タスクの実行結果を表示する。

ビルド プロファイル レポートが作成されたら、レポートの各タブの情報を調べて、最適化の機会を探すことができます。一部のビルド設定は、プロジェクトやワークステーションによってメリットが異なるため、テストが必要です。たとえば、コードベースが大きいプロジェクトでは、コード圧縮を使用して未使用のコードを削除し、アプリのサイズを縮小できます。ただし、小規模なプロジェクトでは、コード圧縮を完全に無効にした方がメリットが大きい場合があります。また、Gradle ヒープサイズを増やすと(org.gradle.jvmargs を使用)、メモリの少ないマシンではパフォーマンスに悪影響を及ぼす可能性があります。

ビルド構成を変更したら、前の手順を繰り返して新しいビルド プロファイルを生成し、変更の結果を確認します。たとえば、図 4 は、このページで説明した基本的な最適化をいくつか適用した後の同じサンプルアプリのレポートを示しています。

図 4. ビルド速度を最適化した後の新しいレポートを表示する