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
を使用すると、測定の統計的有意性を高めることができます。
経過時間は、経過した実際の時間です。
- 実行時間がのみ低下する場合は、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)と、永続ワーカーが使用するメモリの量を減らすことができます。Google は、今後このような微調整の必要性が軽減されるように、Bazel のリソース マネージャーとスケジューラのスマート化に積極的に取り組んでいます。
リモートビルドのネットワーク トラフィックのモニタリング
リモート実行では、Bazel はアクションの実行の結果としてビルドされたアーティファクトをダウンロードします。そのため、ネットワーク帯域幅がビルドのパフォーマンスに影響する可能性があります。
ビルドにリモート実行を使用している場合は、BEP の NetworkMetrics.SystemNetworkStats
proto を使用して、呼び出し中のネットワーク トラフィックをモニタリングすることを検討してください(--experimental_collect_system_network_usage
を渡す必要があります)。
さらに、JSON トレース プロファイルを使用すると、--experimental_collect_system_network_usage
フラグを渡すことで、ビルド全体のシステム全体のネットワーク使用状況を確認できます(Bazel 6.0 の新機能)。
図 3. システム全体のネットワーク使用量を含むプロファイル。
リモート実行を使用しているときにネットワーク使用量が高く、変化が少ない場合は、ネットワークがビルドのボトルネックになっている可能性があります。まだ使用していない場合は、--remote_download_minimal
を渡して、バイトなしビルドを有効にすることを検討してください。これにより、不要な中間アーティファクトのダウンロードを回避して、ビルド速度を向上させることができます。
また、ローカル ディスク キャッシュを構成して、ダウンロード帯域幅を節約することもできます。