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

問題を報告 ソースを表示

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 bench にも、この指標のベンチマークが用意されています。
    • さらに、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 Trace Profiler は、--experimental_collect_system_network_usage フラグを渡すことで、呼び出し中の永続的なワーカーのメモリ使用量も収集します(Bazel 6.0 の新機能)。

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

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

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

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

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

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

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

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

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

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

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