動的実行

バージョン 0.21 以降の 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 ニーモニックのデフォルトがオーバーライドされます。リモート バージョンの場合も同様です。どちらのフラグも複数回指定できます。ローカルで実行できないアクションは通常どおりリモートで実行されます。その逆も同様です。

リモート システムにキャッシュがある場合、--local_execution_delay フラグを指定すると、リモート システムがキャッシュ ヒットを示した後、ローカル実行にミリ秒単位で遅延が発生します。これにより、キャッシュ ヒットの可能性が高い場合に、ローカル実行を回避できます。デフォルト値は 1, 000 ミリ秒ですが、通常キャッシュ ヒットに要する時間よりも少し長くなるように調整する必要があります。実際の時間は、リモート システムとラウンドトリップ時間の両方によって異なります。通常、ラウンドトリップ レイテンシが増加するのに十分な距離にあるユーザーがいる場合を除き、特定のリモート システムのすべてのユーザーが同じ値になります。Bazel プロファイリング機能を使用すると、一般的なキャッシュ ヒットにかかる時間を確認できます。

動的実行は、永続ワーカーだけでなく、ローカルのサンドボックス戦略でも使用できます。永続ワーカーは、動的実行で使用するとサンドボックスで自動的に実行されます。マルチプレックス ワーカーは使用できません。Darwin システムと Windows システムでは、サンドボックス化戦略の動作が遅くなる可能性があります。--reuse_sandbox_directories を渡すと、これらのシステムでサンドボックスを作成するオーバーヘッドを削減できます。

動的実行は standalone 戦略で実行することもできますが、standalone 戦略は実行開始時に出力ロックを取得する必要があるため、リモート戦略が先に終了することを効果的にブロックします。--experimental_local_lockfree_output フラグを使用すると、この問題を回避できます。これにより、ローカル実行で出力に直接書き込むことができます。ただし、先に行われた場合はリモート実行で中断されます。

動的実行のブランチの 1 つが最初に終了したものの失敗した場合、アクション全体が失敗します。これは、ローカル実行とリモート実行の違いが見逃されることを防ぐために、意図的に選択されています。

動的実行とそのロックの仕組みに関する詳しい背景については、Julio Merino の優れたブログ投稿をご覧ください。

動的実行を使うのはどのような場合ですか?

動的実行には、なんらかの形式のリモート実行システムが必要です。キャッシュミスはアクションの失敗と見なされるため、現在、キャッシュ専用のリモート システムは使用できません。

すべてのタイプのアクションがリモート実行に適しているわけではありません。たとえば、永続ワーカー を使用することでローカルに本質的に高速化するソリューションか、またはリモート実行のオーバーヘッドが実行時間の大半を占めるほど高速に実行されるワーカーが最適となります。ローカルで実行される各アクションは、ある程度の CPU リソースとメモリリソースをロックするため、これらのカテゴリに当てはまらないアクションを実行しても、実行が遅れるだけです。

リリース 5.0.0-pre.20210708.4 以降、パフォーマンス プロファイリングには、ワーカーの実行に関するデータ(動的実行競合に失敗した後に処理リクエストの完了に要した時間など)が含まれています。動的実行のワーカー スレッドがリソースの取得にかなりの時間を費やしている場合や、async-worker-finish で多くの時間を費やしている場合は、ワーカー スレッドが遅延するローカル アクションが遅い可能性があります。

動的実行パフォーマンスが低いデータのプロファイリング

8 つの Javac ワーカーを使用する上記のプロファイルでは、多くの Javac ワーカーが競合に負けて async-worker-finish スレッドで作業を終了しています。これは、非ワーカーのニーモニックがワーカーを遅延させるのに十分なリソースを使用していることが原因でした。

動的実行パフォーマンスに優れたデータのプロファイリング

Javac のみを動的実行で実行した場合、開始されたワーカーの約半分のみが処理の開始後に競合に負けます。

以前推奨されていた --experimental_spawn_scheduler フラグは非推奨となりました。これにより、動的実行が有効になり、dynamic がすべてのニーモニックのデフォルト戦略として設定されます。多くの場合、この種の問題が生じます。

トラブルシューティング

動的実行の問題は、ローカル実行とリモート実行の特定の組み合わせでのみ発生する可能性があるため、わかりにくく、デバッグが難しい場合があります。--debug_spawn_scheduler は、こうした問題のデバッグに役立つ動的実行システムからの出力を追加します。--local_execution_delay フラグと、リモートジョブとローカルジョブの数を調整して、問題の再現を容易にすることもできます。

standalone 戦略を使用した動的実行で問題が発生した場合は、--experimental_local_lockfree_output を使用せずに実行してみるか、ローカル アクションをサンドボックス化して実行してください。これにより、ビルドが多少遅くなる可能性があります(Mac または Windows の場合は上記をご覧ください)。ただし、エラーの原因となる問題は解消できます。