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

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` の数を十分に増やすと、測定の統計的有意性を高めることができます。--runs

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

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

    • 2 つのプロジェクト コミット間で CPU 時間が回帰する場合は、Starlark CPU プロファイルを収集することをおすすめします。また、--nobuild を使用してビルドを分析フェーズに制限することをおすすめします。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 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 のメモリ使用量をより詳細に分析するには、ルール用の組み込みメモリ プロファイラを使用することをおすすめします。

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

永続ワーカーはビルドを大幅に高速化できますが(特にインタープリタ言語の場合)、メモリ 使用量が問題になる可能性があります。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 の protoNetworkMetrics.SystemNetworkStats を渡す必要があります)を使用して、呼び出し中のネットワーク トラフィックをモニタリングすることを検討してください。 --experimental_collect_system_network_usage

さらに、JSON トレース プロファイルでは、 フラグ(Bazel 6.0 で新しく追加)を渡すことで、ビルド全体にわたってシステム全体のネットワーク使用量を確認できます。--experimental_collect_system_network_usage

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

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

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

もう 1 つの方法は、ダウンロード帯域幅を節約するためにローカル ディスク キャッシュを 構成することです。