Bazel は複雑で、ビルドの過程でさまざまな処理を行います。その一部はビルドのパフォーマンスに影響する可能性があります。このページでは、これらの Bazel のコンセプトとビルド パフォーマンスへの影響をマッピングします。包括的ではありませんが、指標を抽出することでビルド パフォーマンスの問題を検出する方法と、問題を修正するためにできることの例をいくつか紹介します。この情報が、ビルド パフォーマンスの低下を調査する際に役立つことを願っています。
クリーンビルドと増分ビルド
クリーンビルドはすべてをゼロからビルドするのに対し、増分ビルドはすでに完了した作業の一部を再利用します。
特に、Bazel のキャッシュの状態に依存する指標(ビルド リクエスト サイズ指標など)を収集または集計する場合は、クリーンビルドと増分ビルドを別々に確認することをおすすめします。これらは 2 つの異なるユーザー エクスペリエンスも表しています。クリーンビルドを最初から開始する場合(コールド キャッシュのため時間がかかる)と比較して、増分ビルドはデベロッパーがコードを反復処理する際に頻繁に発生します(通常、キャッシュはすでにウォーム状態であるため、高速です)。
BEP の CumulativeMetrics.num_analyses フィールドを使用して、ビルドを分類できます。num_analyses <= 1 の場合、クリーンビルドです。それ以外の場合は、増分ビルドである可能性が高いと大まかに分類できます。ユーザーが別のフラグや別のターゲットに切り替えたことで、実質的にクリーンビルドになった可能性があります。増分性のより厳密な定義は、ヒューリスティックの形式で提供される可能性があります。たとえば、読み込まれたパッケージの数(PackageMetrics.packages_loaded)を確認するなどです。
ビルド パフォーマンスのプロキシとしての決定論的ビルド指標
特定の指標(Bazel の CPU 時間やリモート クラスタのキュー時間など)の非決定論的な性質により、ビルドのパフォーマンスの測定は困難な場合があります。そのため、決定論的指標を Bazel の作業量の代理として使用すると、パフォーマンスに影響する可能性があります。
ビルド リクエストのサイズは、ビルドのパフォーマンスに大きな影響を与える可能性があります。ビルドが大きいほど、ビルドグラフの分析と構築に時間がかかる可能性があります。ビルドの有機的な成長は、開発に伴って自然に発生します。依存関係が追加または作成されると、複雑さが増し、ビルドのコストが増加します。
この問題をさまざまなビルドフェーズに分割し、次の指標を各フェーズで行われた作業のプロキシ指標として使用できます。
PackageMetrics.packages_loaded: 正常に読み込まれたパッケージの数。ここでの回帰は、読み込みフェーズで追加の BUILD ファイルを読み取って解析するために必要な作業の増加を表します。TargetMetrics.targets_configured: ビルドで構成されたターゲットとアスペクトの数を表します。回帰は、構成されたターゲット グラフの構築とトラバースの作業が増えることを表します。- これは多くの場合、依存関係の追加と、その推移的閉包のグラフの構築が必要になることが原因です。
- cquery を使用して、新しい依存関係が追加された可能性のある場所を見つけます。
ActionSummary.actions_created: ビルドで作成されたアクションを表します。回帰は、アクション グラフの構築における作業の増加を表します。これには、実行されていない未使用のアクションも含まれます。- 回帰のデバッグには aquery を使用します。
--skyframe_stateでさらに絞り込む前に、--output=summaryから始めることをおすすめします。
- 回帰のデバッグには aquery を使用します。
ActionSummary.actions_executed: 実行されたアクションの数。回帰は、これらのアクションの実行における作業量の増加を直接表します。- BEP は、最も実行されたアクション タイプを示すアクション統計
ActionDataを書き出します。デフォルトでは、上位 20 個のアクション タイプが収集されますが、--experimental_record_metrics_for_all_mnemonicsを渡して、実行されたすべてのアクション タイプについてこのデータを収集することもできます。 - これにより、どのようなアクションが(追加で)実行されたかを把握できます。
- BEP は、最も実行されたアクション タイプを示すアクション統計
BuildGraphSummary.outputArtifactCount: 実行されたアクションによって作成されたアーティファクトの数。- 実行されたアクションの数が増加していない場合は、ルール実装が変更された可能性があります。
これらの指標はすべてローカル キャッシュの状態の影響を受けるため、これらの指標を抽出するビルドがクリーンビルドであることを確認する必要があります。
これらの指標のいずれかの回帰は、経過時間、CPU 時間、メモリ使用量の回帰を伴う可能性があることがわかっています。
ローカル リソースの使用量
Bazel は、ローカルマシン上のさまざまなリソースを使用します(ビルドグラフの分析と実行の推進、ローカル アクションの実行の両方)。これにより、ビルドの実行や他のタスクの実行におけるマシンのパフォーマンスや可用性に影響を与える可能性があります。
かかった時間
ノイズの影響を最も受けやすく、ビルドごとに大きく異なる可能性のある指標は、時間(特に実時間、CPU 時間、システム時間)です。bazel-bench を使用してこれらの指標のベンチマークを取得できます。十分な数の --runs を使用すると、測定の統計的有意性を高めることができます。
Wall time は、経過した実際の時間です。
- ウォールタイムのみが回帰する場合は、JSON トレース プロファイルを収集して、違いを探すことをおすすめします。そうでない場合は、ウォールタイムに影響した可能性がある他の回帰指標を調査する方が効率的です。
CPU 時間は、CPU がユーザーコードの実行に費やした時間です。
- 2 つのプロジェクト コミット間で CPU 時間が回帰する場合は、Starlark CPU プロファイルを収集することをおすすめします。また、CPU に負荷のかかる作業のほとんどが分析フェーズで行われるため、
--nobuildを使用してビルドを分析フェーズに制限することをおすすめします。
- 2 つのプロジェクト コミット間で CPU 時間が回帰する場合は、Starlark CPU プロファイルを収集することをおすすめします。また、CPU に負荷のかかる作業のほとんどが分析フェーズで行われるため、
システム時間は、CPU がカーネルで費やした時間です。
- システム時間が回帰する場合、Bazel がファイル システムからファイルを読み取るときに I/O と相関関係があることがほとんどです。
システム全体の負荷プロファイリング
Bazel 6.0 で導入された --experimental_collect_load_average_in_profiler フラグを使用すると、JSON トレース プロファイラは呼び出し中のシステム負荷平均を収集します。

図 1. システム ロード アベレージを含むプロファイル。
Bazel 呼び出し中の高負荷は、Bazel がマシンに対してローカル アクションを並行してスケジュールしすぎていることを示している可能性があります。特にコンテナ環境では、--local_cpu_resources と --local_ram_resources の調整を検討してください(少なくとも #16512 がマージされるまでは)。
Bazel のメモリ使用量をモニタリングする
Bazel のメモリ使用量を取得する主な方法は、Bazel info と BEP の 2 つです。
bazel info used-heap-size-after-gc:System.gc()の呼び出し後の使用済みメモリ量(バイト単位)。- Bazel bench でも、この指標のベンチマークが提供されます。
- また、
peak-heap-size、max-heap-size、used-heap-size、committed-heap-sizeもありますが(ドキュメントを参照)、関連性は低くなります。
BEP の
MemoryMetrics.peak_post_gc_heap_size: GC 後の JVM ヒープのピークサイズ(バイト単位)。(完全な GC を強制的に実行しようとする--memory_profileの設定が必要です)。
メモリ使用量の回帰は通常、ビルド リクエスト サイズ指標の回帰の結果です。これは、依存関係の追加やルール実装の変更が原因であることがよくあります。
Bazel のメモリ フットプリントをより詳細に分析するには、ルールの組み込みメモリ プロファイラを使用することをおすすめします。
永続ワーカーのメモリ プロファイリング
永続ワーカーはビルドを大幅に高速化できますが(特にインタープリタ型言語の場合)、メモリ フットプリントが問題になる可能性があります。Bazel はワーカーに関する指標を収集します。特に、WorkerMetrics.WorkerStats.worker_memory_in_kb フィールドは、ワーカーが使用するメモリ量(ニーモニック別)を示します。
JSON トレース プロファイラは、--experimental_collect_system_network_usage フラグ(Bazel 6.0 で新しく追加)を渡すことで、呼び出し中の永続ワーカーのメモリ使用量も収集します。

図 2. ワーカーのメモリ使用量を含むプロファイル。
--worker_max_instances の値を小さくすると(デフォルトは 4)、永続ワーカーで使用されるメモリ量を減らすことができます。Bazel のリソース マネージャーとスケジューラをよりスマートにして、このような微調整が将来的に必要なくなるよう、積極的に取り組んでいます。
リモートビルドのネットワーク トラフィックをモニタリングする
リモート実行では、Bazel はアクションの実行結果としてビルドされたアーティファクトをダウンロードします。そのため、ネットワーク帯域幅はビルドのパフォーマンスに影響する可能性があります。
ビルドにリモート実行を使用している場合は、BEP の NetworkMetrics.SystemNetworkStats proto を使用して、呼び出し中のネットワーク トラフィックのモニタリングを検討してください(--experimental_collect_system_network_usage の受け渡しが必要です)。
さらに、JSON トレース プロファイルを使用すると、--experimental_collect_system_network_usage フラグ(Bazel 6.0 で新しく追加)を渡すことで、ビルド全体にわたるシステム全体のネットワーク使用状況を確認できます。

図 3. システム全体のネットワーク使用量を含むプロファイル。
リモート実行を使用しているときにネットワーク使用率が高く、かつフラットな場合は、ネットワークがビルドのボトルネックになっている可能性があります。まだ使用していない場合は、--remote_download_minimal を渡して、バイトなしのビルドを有効にすることを検討してください。これにより、不要な中間アーティファクトのダウンロードを回避して、ビルドを高速化できます。
別の方法として、ローカルのディスク キャッシュを構成して、ダウンロード帯域幅を節約することもできます。