このページでは、永続ワーカーの使用方法、メリット、要件、ワーカーがサンドボックスに与える影響について説明します。
永続ワーカーは、Bazel サーバーによって開始される長時間実行プロセスです。これは、実際のツール(通常はコンパイラ)のラッパーとして機能するか、ツール自体です。永続ワーカーのメリットを活かすには、ツールがコンパイルのシーケンスの実行をサポートしている必要があります。また、ラッパーは、ツールの API と以下で説明するリクエスト/レスポンス形式の間で変換を行う必要があります。同じワーカーが同じビルド内で --persistent_worker
フラグの有無にかかわらず呼び出されることがあり、ツールを適切に起動して通信し、終了時にワーカーをシャットダウンする責任を負います。各ワーカー インスタンスには、<outputBase>/bazel-workers
の下に個別の作業ディレクトリが割り当てられます(ただし、chroot はされません)。
永続ワーカーの使用は、起動オーバーヘッドを減らし、より多くの JIT コンパイルを可能にし、アクション実行での抽象構文木などのキャッシュ保存を可能にする実行戦略です。この戦略では、長時間実行されるプロセスに複数のリクエストを送信することで、これらの改善を実現します。
永続ワーカーは、Java、Scala、Kotlin など、複数の言語で実装されています。
NodeJS ランタイムを使用するプログラムは、@bazel/worker ヘルパー ライブラリを使用してワーカー プロトコルを実装できます。
永続ワーカーの使用
Bazel 0.27 以降では、ビルドの実行時にデフォルトで永続ワーカーが使用されますが、リモート実行が優先されます。永続ワーカーをサポートしていないアクションの場合、Bazel は各アクションのツール インスタンスの起動にフォールバックします。該当するツールニーモニックの worker
strategy を設定することで、永続ワーカーを使用するようにビルドを明示的に設定できます。ベスト プラクティスとして、この例では worker
戦略のフォールバックとして local
を指定しています。
bazel build //my:target --strategy=Javac=worker,local
ローカル戦略の代わりにワーカー戦略を使用すると、実装によってはコンパイル速度が大幅に向上する可能性があります。Java の場合、ビルドは 2 ~ 4 倍速くなります。増分コンパイルの場合は、さらに速くなることもあります。ワーカーを使用すると、Bazel のコンパイルが約 2.5 倍速くなります。詳細については、ワーカー数の選択をご覧ください。
ローカル ビルド環境と一致するリモート ビルド環境がある場合は、リモート実行とワーカー実行を競合させる試験運用版の動的戦略を使用できます。動的戦略を有効にするには、--experimental_spawn_scheduler フラグを渡します。この戦略ではワーカーが自動的に有効になるため、worker
戦略を指定する必要はありませんが、local
または sandboxed
をフォールバックとして使用できます。
ワーカー数の選択
ニーモニックあたりのワーカー インスタンスのデフォルト数は 4 ですが、worker_max_instances
フラグで調整できます。使用可能な CPU を有効活用することと、JIT コンパイルとキャッシュ ヒットの量との間にはトレードオフがあります。ワーカーが増えると、JIT 以外のコードを実行してコールド キャッシュをヒットさせる起動費用を支払うターゲットが増えます。ビルドするターゲットの数が少ない場合は、単一のワーカーでコンパイル速度とリソース使用量のバランスが最適になることがあります(たとえば、問題 #8586 を参照)。worker_max_instances
フラグは、ニーモニックとフラグセット(下記参照)あたりのワーカー インスタンスの最大数を設定します。そのため、混合システムでデフォルト値を維持すると、かなりのメモリを使用する可能性があります。増分ビルドの場合、複数のワーカー インスタンスのメリットはさらに小さくなります。
このグラフは、6 コアのハイパースレッド Intel Xeon 3.5 GHz Linux ワークステーション(64 GB の RAM を搭載)での Bazel(ターゲット //src:bazel
)のゼロからのコンパイル時間を示しています。各ワーカー構成に対して 5 回のクリーンビルドが実行され、最後の 4 回の平均が取得されます。
図 1. クリーンビルドのパフォーマンス改善のグラフ。
この構成では、2 つのワーカーでコンパイルが最速になりますが、1 つのワーカーと比較して 14% しか改善されません。使用するメモリを少なくしたい場合は、ワーカーを 1 つにするのが適しています。
通常、増分コンパイルはさらにメリットがあります。クリーンビルドは比較的まれですが、コンパイル間で単一のファイルを変更することは一般的です。特にテスト駆動開発ではよく行われます。上記の例には、増分コンパイル時間を隠す可能性のある Java 以外のパッケージング アクションも含まれています。
AbstractContainerizingSandboxedSpawn.java の内部文字列定数を変更した後に Java ソースのみを再コンパイル(//src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar
)すると、3 倍の高速化が実現します(1 回のウォームアップ ビルドを破棄した 20 回の増分ビルドの平均)。
図 2. 増分ビルドのパフォーマンス改善のグラフ。
高速化の程度は、行われる変更によって異なります。上記の状況で、一般的に使用される定数を変更すると、6 倍の高速化が測定されます。
永続ワーカーの変更
--worker_extra_flag
フラグを渡して、ニーモニックをキーとしてワーカーに起動フラグを指定できます。たとえば、--worker_extra_flag=javac=--debug
を渡すと、Javac のデバッグのみが有効になります。このフラグの使用ごとに設定できるワーカーフラグは 1 つのみで、1 つのニーモニックに対してのみ設定できます。ワーカーは、ニーモニックごとに個別に作成されるだけでなく、起動フラグのバリエーションごとにも作成されます。ニーモニックと起動フラグの各組み合わせは WorkerKey
にまとめられ、各 WorkerKey
に対して最大 worker_max_instances
個のワーカーを作成できます。アクション構成で設定フラグを指定する方法については、次のセクションをご覧ください。
--high_priority_workers
フラグを使用すると、通常の優先度のニーモニックよりも優先して実行するニーモニックを指定できます。これにより、常にクリティカル パスにあるアクションの優先順位を付けることができます。リクエストを実行している優先度の高いワーカーが 2 つ以上ある場合、他のすべてのワーカーは実行されません。このフラグは複数回使用できます。
--worker_sandboxing
フラグを渡すと、各ワーカー リクエストはすべての入力に個別のサンドボックス ディレクトリを使用します。サンドボックスの設定には、特に macOS で追加の時間がかかりますが、正確性の保証が向上します。
--worker_quit_after_build
フラグは、主にデバッグとプロファイリングに役立ちます。このフラグは、ビルドが完了するとすべてのワーカーを強制的に終了させます。--worker_verbose
を渡して、ワーカーの動作に関する詳細な出力を取得することもできます。このフラグは WorkRequest
の verbosity
フィールドに反映され、ワーカー実装もより詳細な情報を出力できるようになります。
ワーカーは、<outputBase>/bazel-workers
ディレクトリ(/tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log
など)にログを保存します。ファイル名には、ワーカー ID とニーモニックが含まれます。ニーモニックごとに複数の WorkerKey
が存在する場合があるため、特定のニーモニックに対して worker_max_instances
個を超えるログファイルが表示されることがあります。
Android ビルドについては、Android ビルドのパフォーマンス ページで詳細をご覧ください。
永続ワーカーの実装
ワーカーの作成方法の詳細については、永続ワーカーの作成のページをご覧ください。
次の例は、JSON を使用するワーカーの Starlark 構成を示しています。
args_file = ctx.actions.declare_file(ctx.label.name + "_args_file")
ctx.actions.write(
output = args_file,
content = "\n".join(["-g", "-source", "1.5"] + ctx.files.srcs),
)
ctx.actions.run(
mnemonic = "SomeCompiler",
executable = "bin/some_compiler_wrapper",
inputs = inputs,
outputs = outputs,
arguments = [ "-max_mem=4G", "@%s" % args_file.path],
execution_requirements = {
"supports-workers" : "1", "requires-worker-protocol" : "json" }
)
この定義では、このアクションの最初の使用は、コマンドライン /bin/some_compiler -max_mem=4G --persistent_worker
の実行から始まります。Foo.java
をコンパイルするリクエストは次のようになります。
注: プロトコル バッファ仕様では「スネークケース」(request_id
)が使用されますが、JSON プロトコルでは「キャメルケース」(requestId
)が使用されます。このドキュメントでは、JSON の例ではキャメルケースを使用しますが、プロトコルに関係なくフィールドについて説明する場合はスネークケースを使用します。
{
"arguments": [ "-g", "-source", "1.5", "Foo.java" ]
"inputs": [
{ "path": "symlinkfarm/input1", "digest": "d49a..." },
{ "path": "symlinkfarm/input2", "digest": "093d..." },
],
}
ワーカーは、改行区切りの JSON 形式で stdin
でこれを受け取ります(requires-worker-protocol
が JSON に設定されているため)。ワーカーはアクションを実行し、JSON 形式の WorkResponse
を stdout で Bazel に送信します。Bazel はこのレスポンスを解析し、手動で WorkResponse
proto に変換します。JSON ではなくバイナリ エンコードされた protobuf を使用して関連付けられたワーカーと通信するには、次のように requires-worker-protocol
を proto
に設定します。
execution_requirements = {
"supports-workers" : "1" ,
"requires-worker-protocol" : "proto"
}
実行要件に requires-worker-protocol
を含めない場合、Bazel はデフォルトでワーカー通信に protobuf を使用します。
Bazel はニーモニックと共有フラグから WorkerKey
を導出するため、この構成で max_mem
パラメータの変更が許可されている場合、使用される値ごとに個別のワーカーが生成されます。バリエーションを多用すると、メモリ消費量が過剰になる可能性があります。
現在、各ワーカーが一度に処理できるリクエストは 1 つのみです。試験運用版の多重ワーカー機能を使用すると、基盤となるツールがマルチスレッドで、このことを理解するようにラッパーが設定されている場合に、複数のスレッドを使用できます。
この GitHub リポジトリでは、Java と Python で記述されたワーカー ラッパーの例を確認できます。JavaScript または TypeScript で作業している場合は、@bazel/worker パッケージと nodejs ワーカーの例が役立つ可能性があります。
ワーカーはサンドボックスにどのような影響を与えますか?
デフォルトで worker
戦略を使用しても、local
戦略と同様に、サンドボックスでアクションは実行されません。--worker_sandboxing
フラグを設定して、すべてのワーカーをサンドボックス内で実行できます。これにより、ツールの各実行で、想定される入力ファイルのみが認識されるようになります。ツールは、リクエスト間で内部的に(たとえばキャッシュを介して)情報を漏洩する可能性があります。dynamic
戦略を使用するには、ワーカーをサンドボックス化する必要があります。
ワーカーでコンパイラ キャッシュを正しく使用できるように、各入力ファイルとともにダイジェストが渡されます。そのため、コンパイラまたはラッパーは、ファイルを読み取らなくても入力が有効かどうかを確認できます。
入力ダイジェストを使用して不要なキャッシュ保存を防ぐ場合でも、サンドボックス化されたワーカーは、ツールが以前のリクエストの影響を受けた他の内部状態を保持している可能性があるため、純粋なサンドボックスよりも厳格なサンドボックス化を提供しません。
多重化ワーカーは、ワーカー実装がサポートしている場合にのみサンドボックス化できます。このサンドボックス化は、--experimental_worker_multiplex_sandboxing
フラグで個別に有効にする必要があります。詳しくは、設計ドキュメントをご覧ください。
参考資料
永続ワーカーの詳細については、以下をご覧ください。