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

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

Android Studio 4.0 以上を使用している場合は、Build Analyzer を使用することで、ビルドのパフォーマンスに関する問題を調査できます。

さらに、Android Studio 以外でビルドのプロファイリングを行うには、次の 2 つの方法があります。

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

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

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

最適なビルド速度を提供するプロジェクト設定を見つけるには、Gradle プロファイラを使用します。これは、Gradle ビルドのプロファイリングとベンチマーク情報を収集するためのツールです。 Gradle プロファイラを使用すると、ビルドシナリオを作成して複数回実行できるため、それぞれの結果が大きく異ならないようして、結果の再現性を確保できます。

クリーンビルドと増分ビルドに関する情報の収集にはベンチマーク モードを使用する必要がありますが、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 プロファイラは、増分ビルドのプロファイリングを幅広くサポートしています。 メソッド本体の変更、新しいメソッドの追加、レイアウトまたは文字列リソースの変更によって、ソースファイルに自動的に変更を適用できます。たとえば、次のような増分シナリオを作成できます。

# <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 &lt;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 assembleFlavorDebug
    
    • --profile: プロファイリングを有効にします。
    • --offline: Gradle によるオンライン依存関係の取得を無効にします。これにより、Gradle が依存関係の更新を試みる際の遅延がプロファイリングに及ぼす影響を回避できます。Gradle が依存関係をダウンロードしてキャッシュに保存した状態にするには、プロジェクトを先に 1 回ビルドしておきます。
    • --rerun-tasks: Gradle にすべてのタスクの再実行を強制し、タスクの最適化は無視します。
  4. 図 1. プロファイル レポートの場所を示すプロジェクト ビュー

    ビルドが完了したら、[Project] ウィンドウを使用して、project-root/build/reports/profile/ ディレクトリに移動します(図 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:processDevUniversalDebugManifestUP-TO-DATE のマークがない場合、現在のビルド構成で、このマニフェスト ファイルはビルドのたびに動的に更新されている可能性があります。ただし、:app:checkDevDebugManifest などの一部のタスクは、ビルドのたびに実行する必要があります。

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

ビルド プロファイル レポートが手に入ったので、レポートの各タブの情報を調べて、最適化できる部分がないか探すことができます。ビルド設定の一部は、プロジェクトやワークステーションによって効果が異なることがあるので、さまざまな設定を試す必要があります。たとえば、大規模なコードベースを含むプロジェクトの場合、コードを圧縮して、使用されていないコードを除去し、アプリサイズを縮小すると、効果が得られます。他方、小規模なプロジェクトの場合は、コード圧縮を無効にしたほうが効果が大きいこともあります。また、メモリの少ないマシンで org.gradle.jvmargs を使用して Gradle のヒープサイズを増やすと逆効果になることがあります。

ビルド構成を変更したら、上記の手順を繰り返して、新しいビルド プロファイルを生成し、変更の効果を確認します。このページで説明した基本的な最適化手法を、同じサンプルアプリに一部適用したレポートの例を図 4 に示します。

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