ビルドのパフォーマンスの内訳

問題を報告 ソースを表示 毎晩

Bazel は複雑で、ビルドの過程でさまざまな処理を行いますが、その一部はビルドのパフォーマンスに影響する可能性があります。このページでは、これらの Bazel のコンセプトの一部を、ビルド パフォーマンスに与える影響と対応付けていきましょう。指標を抽出することでビルドのパフォーマンスの問題を検出する方法と、その修正方法の例をいくつか紹介します。これで、ビルドのパフォーマンス低下を調査する際に、これらのコンセプトを応用できることを願っています。

クリーンビルドと増分ビルド

クリーンビルドは、すべてをゼロからビルドするビルドで、増分ビルドでは、すでに完了した作業の一部を再利用します。

特に、Bazel のキャッシュの状態に依存する指標(ビルド リクエスト サイズの指標など)を収集または集計する場合は、クリーンビルドと増分ビルドを個別に確認することをおすすめします。また、これらは 2 つの異なるユーザー エクスペリエンスを表します。ゼロからクリーンビルドを開始する場合(コールド キャッシュのため時間がかかるため)と比べると、デベロッパーがコードを反復処理する際に増分ビルドが発生する頻度がはるかに高くなります(通常はキャッシュがすでにウォーム状態になっているため、より高速になります)。

BEP の CumulativeMetrics.num_analyses フィールドを使用してビルドを分類できます。num_analyses <= 1 の場合はクリーンビルドです。そうでない場合は、増分ビルドである可能性として大まかに分類できます。ユーザーが別のフラグや別のターゲットに切り替えて、効果的にクリーンなビルドを発生させた可能性があります。より厳密なインクリメンタリティの定義は、読み込まれたパッケージの数(PackageMetrics.packages_loaded)を確認するなど、ヒューリスティックの形で行う必要があります。

ビルド パフォーマンスのプロキシとして決定的なビルド指標

特定の指標の非決定的な性質(Bazel の CPU 時間やリモート クラスタでのキュー時間など)のために、ビルド パフォーマンスの測定が困難な場合があります。そのため、決定論的な指標を Bazel で行った作業量の代用として使用すると役立つ場合があります。これは Bazel のパフォーマンスに影響を与えます。

ビルド リクエストのサイズは、ビルドのパフォーマンスに大きな影響を与える可能性があります。ビルドが大きいほど、ビルドグラフの分析と構築の作業が多くなります。ビルドの有機的な成長は、開発に伴って必然的に起こります。依存関係が追加/作成されるため、複雑さが増し、ビルドのコストが高くなります。

この問題をさまざまなビルドフェーズに分割し、各フェーズで行われる作業のプロキシ指標として次の指標を使用できます。

  1. PackageMetrics.packages_loaded: 正常に読み込まれたパッケージの数。この場合の回帰は、読み込みフェーズで追加の BUILD ファイルの読み取りと解析により多くの作業が必要であることを示します。

    • これは多くの場合、依存関係が追加され、推移的クロージャを読み込む必要があるためです。
    • query / cquery を使用して、新しい依存関係が追加された可能性のある場所を確認します。
  2. TargetMetrics.targets_configured: ビルドで構成されているターゲットとアスペクトの数を表します。回帰では、構成したターゲット グラフの作成と走査により多くの作業が必要になります。

    • これは多くの場合、依存関係の追加と、推移的クロージャのグラフを構築する必要があることが原因です。
    • cquery を使用して、新しい依存関係が追加された可能性のある場所を確認します。
  3. ActionSummary.actions_created: ビルド内で作成されるアクションを表し、回帰はアクション グラフの作成における作業の増加を表します。これには、実行されていない可能性のある未使用のアクションも含まれます。

  4. ActionSummary.actions_executed: 実行されたアクションの数。回帰は、これらのアクションの実行に伴う作業量が増えることを直接表します。

    • BEP は、最も多く実行されたアクション タイプを示すアクション統計情報 ActionData を書き出します。デフォルトでは上位 20 個のアクション タイプを収集しますが、--experimental_record_metrics_for_all_mnemonics を渡すことで、実行されたすべてのアクション タイプについてこのデータを収集できます。
    • これにより、実行されたアクションの種類を把握できます(加えて)。
  5. BuildGraphSummary.outputArtifactCount: 実行されたアクションによって作成されたアーティファクトの数。

    • 実行されたアクションの数が増加していない場合、ルールの実装が変更された可能性があります。

これらの指標はすべてローカル キャッシュの状態の影響を受けるため、指標を抽出するビルドがクリーンビルドであることを確認してください。

これらの指標の回帰は、経過時間、CPU 時間、メモリ使用量の回帰を伴う可能性があることを確認しました。

ローカル リソースの使用

Bazel は、(ビルドグラフの分析と実行、ローカル アクションの実行)ローカルマシン上のさまざまなリソースを消費します。これは、ビルドを実行する際のマシンのパフォーマンスや可用性、その他のタスクにも影響する可能性があります。

かかった時間

おそらく、最もノイズの影響を受けやすい指標(ビルドによって大きく異なる可能性があります)は、特に経過時間、CPU 時間、システム時間です。bazel-bench を使用してこれらの指標のベンチマークを取得できます。十分な数の --runs があれば、測定の統計的有意性を向上させることができます。

  • 経過時間は、実際の経過時間です。

    • 経過時間が回帰のみの場合は、JSON トレース プロファイルを収集して差異を探すことをおすすめします。それ以外の場合は、経過時間に影響を与える可能性があるため、他の回帰指標を調査した方が効率的です。
  • CPU 時間は、CPU がユーザーコードを実行するのに費やした時間です。

    • CPU 時間が 2 つのプロジェクトの commit にわたって回帰する場合は、Starlark の CPU プロファイルを収集することをおすすめします。また、--nobuild を使用してビルドを分析フェーズに制限する必要があります。これは、CPU の負荷が高い作業のほとんどが分析フェーズで実行されるためです。
  • システム時間は、カーネル内の CPU が費やした時間です。

    • システム時間が回帰すると、ほとんどの場合、Bazel がファイル システムからファイルを読み取るときの I/O と関連付けられます。

システム全体の負荷プロファイリング

JSON トレース プロファイラは、Bazel 6.0 で導入された --experimental_collect_load_average_in_profiler フラグを使用して、呼び出し中にシステム負荷の平均を収集します。

システム負荷平均を含むプロファイル

図 1. システム負荷平均を含むプロファイル。

Bazel の呼び出し中に負荷が高い場合は、Bazel がマシンに対して並行してスケジュールするローカル アクションの数が多すぎる可能性があります。特にコンテナ環境で、--local_cpu_resources--local_ram_resources の調整を検討することをおすすめします(少なくとも #16512 がマージされるまで)。

Bazel メモリ使用量のモニタリング

Bazel のメモリ使用量は、Bazel infoBEP という 2 つの主なソースで確認できます。

  • bazel info used-heap-size-after-gc: System.gc() の呼び出し後に使用されているメモリの量(バイト単位)。

    • Bazel ベンチでも、この指標のベンチマークが提供されます。
    • さらに、peak-heap-sizemax-heap-sizeused-heap-sizecommitted-heap-size もありますが(ドキュメントを参照)が、あまり関連性がありません。
  • BEPMemoryMetrics.peak_post_gc_heap_size: GC 後のピーク JVM ヒープサイズのサイズ(バイト単位)。完全な GC を強制的に実行するには、--memory_profile を設定する必要があります。

メモリ使用量の回帰は通常、ビルド リクエスト サイズの指標の回帰の結果です。多くの場合、依存関係の追加やルールの実装の変更が原因です。

Bazel のメモリ使用量をより詳細に分析するには、ルールに組み込みの Memory Profiler を使用することをおすすめします。

永続ワーカーのメモリ プロファイリング

永続ワーカーはビルドを大幅に高速化できますが(特にインタプリタ言語の場合)、メモリ使用量が問題となる可能性があります。Bazel はワーカーに関する指標を収集します。特に WorkerMetrics.WorkerStats.worker_memory_in_kb フィールドは、メモリワーカーの使用量を(ニーモニックで)示します。

また、JSON トレース プロファイラは、--experimental_collect_system_network_usage フラグを渡すことで、呼び出し中の永続ワーカーのメモリ使用量を収集します(Bazel 6.0 の新機能)。

ワーカーのメモリ使用量を含むプロファイル

図 2. ワーカーのメモリ使用量を含むプロファイル。

--worker_max_instances の値を小さくすると(デフォルトは 4)、永続ワーカーが使用するメモリ量を削減できる場合があります。Google は、今後このような微調整の必要性が軽減されるように、Bazel のリソース マネージャーとスケジューラのスマート化に積極的に取り組んでいます。

リモートビルドのネットワーク トラフィックのモニタリング

リモート実行では、Bazel はアクションの実行の結果としてビルドされたアーティファクトをダウンロードします。そのため、ネットワーク帯域幅がビルドのパフォーマンスに影響する可能性があります。

ビルドにリモート実行を使用する場合は、BEPNetworkMetrics.SystemNetworkStats プロトコル(--experimental_collect_system_network_usage を渡す必要があります)を使用して、呼び出し中にネットワーク トラフィックをモニタリングすることをおすすめします。

さらに、JSON トレース プロファイル--experimental_collect_system_network_usage フラグを渡すことで、ビルドの過程でシステム全体のネットワーク使用状況を確認できます(Bazel 6.0 の新機能)。

システム全体のネットワーク使用状況を含むプロファイル

図 3. システム全体のネットワーク使用状況を含むプロファイル。

リモート実行の使用時にネットワーク使用率が高いもののかなりフラットな場合、ネットワークがビルドのボトルネックになっている可能性があります。まだ使用していない場合は、--remote_download_minimal を渡してバイトなしでビルドを有効にすることを検討してください。これにより、不要な中間アーティファクトのダウンロードが回避され、ビルドが高速化されます。

また、ダウンロード帯域幅を節約するためにローカル ディスク キャッシュを構成する方法もあります。