このページでは、永続ワーカーを使用する方法、利点、要件、ワーカーがサンドボックスに与える影響について説明します。
永続ワーカーは、Bazel サーバーによって開始される長時間実行プロセスであり、実際のツール(通常はコンパイラ)のラッパー、またはツール自体として機能します。永続的なワーカーの利点を活用するには、ツールが一連のコンパイルの実行をサポートする必要があり、ラッパーはツールの API と以下で説明するリクエスト/レスポンス形式の間で変換する必要があります。同じビルド内で --persistent_worker
フラグの有無にかかわらず、同じワーカーが呼び出される可能性があり、ツールを適切に起動して通信し、終了時にワーカーをシャットダウンする責任があります。各ワーカー インスタンスは、<outputBase>/bazel-workers
に個別の作業ディレクトリが割り当てられています(ただし、ルーティングされていません)。
永続ワーカーの使用は、起動のオーバーヘッドを削減し、より多くの JIT コンパイルを行い、たとえばアクション実行の抽象構文ツリーをキャッシュに保存できるようにする実行戦略です。この戦略では、長時間実行プロセスに複数のリクエストを送信することで、これらの改善を実現します。
永続ワーカーは、Java、Scala、Kotlin など、複数の言語に対して実装されます。
NodeJS ランタイムを使用するプログラムは、@bazel/worker ヘルパー ライブラリを使用してワーカー プロトコルを実装できます。
永続ワーカーを使用する
Bazel 0.27 以降の場合、ビルドの実行時にデフォルトで永続ワーカーが使用されますが、リモート実行が優先されます。永続ワーカーをサポートしないアクションの場合、Bazel はアクションごとにツール インスタンスを使用するようフォールバックします。該当するツールの略語に worker
戦略を設定することで、永続ワーカーを使用するようにビルドを明示的に設定できます。この例では、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 以外のコードを実行してコールド キャッシュにヒットするという起動コストが増加します。ビルドするターゲットの数が少ない場合、1 つのワーカーでコンパイル速度とリソース使用量との最適なトレードオフが発生する可能性があります(たとえば、問題 #8586 をご覧ください)。worker_max_instances
フラグは、ニーモニックとフラグセットごとのワーカー インスタンスの最大数を設定します(以下を参照)。混合型システムでは、デフォルト値をそのままにしておくと、大量のメモリが使用されることになります。増分ビルドの場合、複数のワーカー インスタンスの利点はさらに小さくなります。
このグラフは、64 GB の RAM を搭載した 6 コアのハイパースレッド Intel Xeon 3.5 GHz Linux ワークステーションで、Bazel(ターゲット //src:bazel
)のゼロからコンパイルする時間を示しています。ワーカー構成ごとに、5 つのクリーンビルドが実行され、最後の 4 個の平均が取得されます。
図 1. クリーンビルドのパフォーマンスのグラフ。
この構成では、2 つのワーカーによりコンパイルが高速になりますが、1 つのワーカーに比べてわずか 14% の改善です。メモリ使用量を減らしたい場合は、1 個のワーカーが適しています。
増分コンパイルでは通常、さらにメリットがあります。クリーンビルドは比較的まれですが、特にテストドリブンな開発では、コンパイル後に 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
フラグを使用して、通常の優先度のニーモニックよりも優先される略語を指定できます。これにより、常にクリティカル パスに存在するアクションに優先順位を付けることができます。優先度の高いワーカーがリクエストを実行している場合、他のすべてのワーカーは実行されません。このフラグは複数回使用できます。
--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 と略称が含まれます。1 つのニーモニックに複数の 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..." },
],
}
ワーカーは、stdin
requires-worker-protocol
が JSON に設定されているため、改行区切りの JSON 形式で受信されます。ワーカーはこのアクションを行い、stdout で JSON 形式の WorkResponse
を 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 つのリクエストしか処理できません。基礎となるツールがマルチスレッドで、ラッパーがこれを理解できるように設定されている場合、試験運用版のMultiplex ワーカー機能では複数のスレッドを使用できます。
この GitHub リポジトリでは、Java と Python で記述されたワーカー ラッパーの例を確認できます。JavaScript または TypeScript で作業している場合は、@bazel/worker パッケージと nodejs ワーカーのサンプルが便利です。
ワーカーはサンドボックス化にどのように影響しますか?
worker
戦略をデフォルトで使用しても、local
戦略と同様に、サンドボックスではアクションを実行しません。サンドボックス内のすべてのワーカーを実行するように --worker_sandboxing
フラグを設定すると、ツールを実行するたびに想定された入力ファイルのみが表示されるように設定できます。ただしこのツールは、キャッシュを使用するなどして、リクエスト間で情報を漏洩する可能性があります。dynamic
戦略を使用するには、ワーカーをサンドボックス化する必要があります。
ワーカーでのコンパイラ キャッシュを正しく使用できるように、各入力ファイルとともにダイジェストが渡されます。そのため、コンパイラまたはラッパーは、ファイルを読み取ることなく、入力がまだ有効かどうかを確認できます。
入力ダイジェストを使用して不要なキャッシュから保護する場合でも、サンドボックス化されたワーカーは、純粋なサンドボックスよりも厳格でないサンドボックス化を行います。これは、このツールが以前のリクエストによって影響された他の内部状態を保持する可能性があるからです。
Multiplex ワーカーをサンドボックス化できるのは、ワーカーの実装でサポートされている場合のみです。このサンドボックスは、--experimental_worker_multiplex_sandboxing
フラグを使用して別途有効にする必要があります。詳細については、設計ドキュメントをご覧ください。
参考資料
永続ワーカーの詳細については、次をご覧ください。