動的実行は、同じアクションのローカル実行とリモート実行が並行して開始され、完了した最初のブランチの出力を使用して、他のブランチがキャンセルされる Bazel の機能です。リモート ビルドシステムの実行能力や大規模な共有キャッシュと、ローカル実行の低レイテンシを組み合わせることで、クリーンビルドと増分ビルドの両方で両方のメリットを活用できます。
このページでは、動的実行を有効にする方法、チューニングする方法、デバッグする方法について説明します。ローカル実行とリモート実行の両方を設定していて、パフォーマンスを向上させるために Bazel の設定を調整しようとしている場合は、このページが役に立ちます。リモート実行がまだ設定されていない場合は、まず Bazel のリモート実行の概要をご覧ください。
動的実行を有効にする
動的実行モジュールは Bazel の一部ですが、動的実行を使用するには、同じ Bazel 設定からローカルとリモートの両方でコンパイルできる必要があります。
動的実行モジュールを有効にするには、--internal_spawn_scheduler
フラグを Bazel に渡します。これにより、dynamic
という新しい実行戦略が追加されます。これで、--strategy=Javac=dynamic
など、動的に実行するメモニクスの戦略として使用できます。動的実行を有効にするメモニカを選択する方法については、次のセクションをご覧ください。
動的戦略を使用するメモニクスの場合、リモート実行戦略は --dynamic_remote_strategy
フラグから取得され、ローカル戦略は --dynamic_local_strategy
フラグから取得されます。--dynamic_local_strategy=worker,sandboxed
を渡すと、動的実行のローカル ブランチのデフォルトが設定され、ワーカーまたはサンドボックス実行がその順序で試行されます。--dynamic_local_strategy=Javac=worker
を渡すと、Javac の頭字語のデフォルトがオーバーライドされます。リモート バージョンも同様に機能します。どちらのフラグも複数回指定できます。アクションをローカルで実行できない場合は、通常どおりリモートで実行されます。その逆も同様です。
リモート システムにキャッシュがある場合、--dynamic_local_execution_delay
フラグは、リモート システムがキャッシュヒットを示した後に、ローカル実行にミリ秒単位の遅延を追加します。これにより、キャッシュヒットが多くなる可能性が高い場合にローカル実行を実行する必要がなくなります。デフォルト値は 1, 000 ミリ秒ですが、通常のキャッシュヒットよりも少し長くなるように調整する必要があります。実際の時間は、リモート システムとラウンドトリップの所要時間の両方によって異なります。通常、この値は、一部のユーザーが十分に離れていてラウンドトリップ レイテンシが増加する場合を除き、特定のリモート システムのすべてのユーザーで同じになります。Bazel プロファイリング機能を使用して、一般的なキャッシュヒットの所要時間を調べることができます。
動的実行は、ローカル サンドボックス化戦略と永続ワーカーで使用できます。永続ワーカーは、動的実行で使用すると自動的にサンドボックスで実行されます。マルチプレックス ワーカーは使用できません。Darwin システムと Windows システムでは、サンドボックス化された戦略が遅くなることがあります。--reuse_sandbox_directories
を渡すと、これらのシステムでサンドボックスを作成するオーバーヘッドを削減できます。
動的実行は standalone
戦略で実行することもできますが、standalone
戦略は実行を開始するときに出力ロックを取得する必要があるため、リモート戦略が先に完了するのを効果的にブロックします。--experimental_local_lockfree_output
フラグを使用すると、ローカル実行が出力に直接書き込むことができますが、リモート実行が先に完了した場合は、リモート実行によって中止されます。これにより、この問題を回避できます。
動的実行のいずれかのブランチが先に完了しても失敗した場合、アクション全体が失敗します。これは、ローカル実行とリモート実行の違いが気付かれないことを防ぐための意図的な選択です。
動的実行とそのロックの仕組みについて詳しくは、Julio Merino の優れたブログ投稿をご覧ください。
動的実行を使用するタイミング
動的実行には、なんらかのリモート実行システムが必要です。現在のところ、キャッシュのみのリモート システムを使用することはできません。キャッシュミスは失敗したアクションと見なされるためです。
すべてのタイプのアクションがリモート実行に適しているわけではありません。最適な候補は、永続ワーカーの使用などにより、ローカルで本質的に高速であるもの、またはリモート実行のオーバーヘッドが実行時間を圧倒するほど高速に実行されるものです。ローカルで実行される各アクションは一定量の CPU リソースとメモリリソースをロックするため、これらのカテゴリに該当しないアクションを実行しても、該当するアクションの実行が遅れるだけです。
リリース 5.0.0-pre.20210708.4 以降、パフォーマンス プロファイリングには、動的実行競合に敗れた後のワーク リクエストの完了に費やされた時間など、ワーカーの実行に関するデータが含まれます。動的実行ワーカー スレッドがリソースの取得にかなりの時間を費やしている場合や、async-worker-finish
で多くの時間を費やしている場合は、ローカル アクションが遅く、ワーカー スレッドが遅れている可能性があります。
8 つの Javac ワーカーを使用する上記のプロファイルでは、多くの Javac ワーカーが競合に敗れ、async-worker-finish
スレッドで作業を完了しています。これは、ワーカー以外のメモニクスがワーカーを遅らせるのに十分なリソースを消費したことが原因でした。
Javac のみを動的実行で実行すると、開始されたワーカーの約半分だけが、作業を開始した後に競合に敗れてしまいます。
以前推奨されていた --experimental_spawn_scheduler
フラグは非推奨になりました。動的実行が有効になり、すべてのメモニカのデフォルト戦略として dynamic
が設定されます。このため、このような問題が発生することがあります。
パフォーマンス
動的実行アプローチでは、ローカルとリモートで十分なリソースが利用可能であり、全体的なパフォーマンスを向上させるために追加のリソースを費やす価値があると想定しています。ただし、リソースの使用量が多すぎると、Bazel 自体またはそのマシンの動作が遅くなったり、リモート システムに予期しない負荷がかかったりする可能性があります。動的実行の動作を変更するには、いくつかの方法があります。
--dynamic_local_execution_delay
は、現在のビルド中にリモート キャッシュにヒットした場合にのみ、リモート ブランチの開始後にローカル ブランチの開始を数ミリ秒遅らせます。これにより、リモート キャッシュを利用できるビルドで、ほとんどの出力がキャッシュに存在する可能性が高い場合に、ローカル リソースを浪費することがなくなります。キャッシュの品質によっては、これを減らすとビルド速度が向上する可能性がありますが、その場合はローカル リソースの使用量が増えます。
--experimental_dynamic_local_load_factor
は、試験運用版の高度なリソース管理オプションです。値は 0 ~ 1 で、0 にするとこの機能は無効になります。0 より大きい値に設定すると、多くのアクションがスケジュールされるのを待機しているときに、Bazel はローカルでスケジュールされるアクションの数を調整します。1 に設定すると、利用可能な CPU の数(--local_cpu_resources
に従う)と同じ数のアクションをスケジュールできます。値を小さくすると、実行可能なアクションの数が増えるため、スケジュールされるアクションの数もそれに応じて少なくなります。直感に反するように思えますが、優れたリモート システムでは、多くのアクションが実行されている場合、ローカル実行はあまり役に立ちません。ローカル CPU はリモート アクションの管理に使用したほうが効果的です。
--experimental_dynamic_slow_remote_time
は、リモート ブランチがこの期間以上実行されている場合に、ローカル ブランチの開始を優先します。通常、レースに勝つ可能性が高いため、最近スケジュールされたアクションが優先されますが、リモート システムがハングしたり、時間がかかりすぎたりすると、ビルドが進むことがあります。これはデフォルトでは有効になっていません。これは、修正すべきリモート システムの問題が隠れてしまう可能性があるためです。このオプションを有効にする場合は、リモート システムのパフォーマンスをモニタリングしてください。
--experimental_dynamic_ignore_local_signals
を使用すると、特定のシグナルによりローカル スポーンが終了したときに、リモート ブランチが引き継ぐようにできます。これは主に、ワーカーのリソースの上限(--experimental_worker_memory_limit_mb
、--experimental_worker_sandbox_hardening
、--experimental_sandbox_memory_limit_mb
を参照)とともに使用されます。ワーカー プロセスは、リソースを使いすぎたときに強制終了される場合があります。
JSON トレース プロファイルには、パフォーマンスとリソース使用量のトレードオフを改善する方法を見つけるのに役立つ、パフォーマンス関連のグラフが多数含まれています。
トラブルシューティング
動的実行の問題は、ローカル実行とリモート実行の特定の組み合わせでのみ発生するため、問題がわかりにくく、デバッグが難しい場合があります。--debug_spawn_scheduler
は、これらの問題のデバッグに役立つ動的実行システムからの追加出力を追加します。--dynamic_local_execution_delay
フラグとリモートジョブとローカルジョブの数を調整して、問題を簡単に再現することもできます。
standalone
戦略を使用した動的実行で問題が発生した場合は、--experimental_local_lockfree_output
なしで実行するか、ローカル アクションをサンドボックス化された状態で実行してみてください。これによりビルドが少し遅くなる可能性があります(Mac または Windows の場合は上記を参照)。ただし、エラーの原因となる可能性のあるものが除外されます。