本页面介绍了如何使用永久性工作器,以及永久性工作器的优势、要求和 工作器如何影响沙盒
永久性工作器是由 Bazel 服务器启动的长时间运行的进程,
用作实际工具(通常是编译器)的封装容器,或者是
工具本身。为了从永久性工作器中受益,该工具必须
支持执行一系列编译,并且封装容器需要将
。相同
调用 worker 时,无论是否在函数中使用 --persistent_worker
标记,
并负责以适当的方式启动
以及在退出时关闭 worker。系统会为每个工作器实例分配
(但不通过 chroot 到)一个单独的工作目录
<outputBase>/bazel-workers
。
使用永久性工作器 执行策略, 启动开销,允许更多的 JIT 编译,并支持缓存 操作执行中的抽象语法树示例。此策略 实现这些改进的方法是,向长时间运行的 过程。
持久性工作器针对多种语言实现,包括 Java、 Scala、 Kotlin 等。
使用 NodeJS 运行时的程序可以使用 @bazel/worker 帮助程序库 实现工作器协议
使用永久性工作器
Bazel 0.27 及更高版本
执行构建时默认使用永久性工作器
优先执行对于不支持永久性工作器的操作
Bazel 会回退到为每个操作启动工具实例。您可以明确指定
通过设置 worker
,将构建设置为使用永久性工作器
适用工具的策略
助记符按照最佳实践,此示例包括将 local
指定为
回退到 worker
策略:
bazel build //my:target --strategy=Javac=worker,local
使用 worker 策略而不是本地策略可以增强编译 具体取决于部署方式。对于 Java,build 可以是 2-4 个 更快,有时用于增量编译。编译 Bazel 的过程如下: 运行速度大约是工作器的 2.5 倍。有关详情,请参阅 “选择工作器数量”部分。
如果您还有与本地构建匹配的远程构建环境
则可以使用实验性功能
动态策略
它与远程执行和工作器执行竞争。要启用动态
传递
--experimental_spawn_scheduler
标志。此策略会自动启用工作器,因此无需
您可以指定 worker
策略,但您仍然可以将 local
或 sandboxed
用作
后备广告。
选择工作器数量
每个助记符的默认工作器实例数为 4,但可以调整
替换为
worker_max_instances
标志。您需要在充分利用可用 CPU 和
您获得的 JIT 编译和缓存命中量。工作器越多
目标将支付运行非 JIT 代码和出现冷启动的启动成本
缓存。如果要构建的目标数量较少,单个 Worker 可以
编译速度和资源用量之间的最佳权衡(例如,
请参阅问题 8586。
worker_max_instances
标志用于设置每个
助记符和标志集(见下文),因此在混合系统中,您最终可能使用
如果您保留默认值,则会获得大量内存。对于增量构建
那么使用多个工作器实例的好处就更小了。
此图表显示了 Bazel(目标
//src:bazel
),在 6 核超线程 Intel Xeon 3.5 GHz Linux 工作站上运行
具有 64 GB RAM对于每个工作器配置,系统会运行五个整洁 build,
计算最后四项指标的平均值。
图 1. 干净 build 的性能改进图。
对于此配置,两个工作器的编译速度最快,尽管编译速度仅为 14% 如果您希望 使用的内存较少
增量编译通常好处更多。干净 build 相对罕见,但在编译之后更改单个文件是很常见的, 特别是在由测试驱动的开发中。上面的示例还具有一些 对增量编译时间执行打包操作。
仅重新编译 Java 源代码
(//src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar
)
更改内部字符串常量之后,
AbstractContainerizingSandboxedSpawn.java
提供 3 倍的速度提升(通过一个预热 build 平均进行 20 次增量构建)
已舍弃):
图 2. 增量构建的性能改进图。
加速时间取决于所做的更改。6 倍的加速倍数为 在上述情况中衡量。
修改永久性工作器
您可以将
--worker_extra_flag
标志,用于为工作器指定启动标志,由助记符键控。例如,
传递 --worker_extra_flag=javac=--debug
仅会为 Javac 开启调试功能。
每次使用此标志时,只能设置一个工作器标志,并且只能为一个助记符设置一个工作器标志。
您不仅要为每个助记符单独创建 Worker,
启动标志。助记符和启动的每种组合
标志会组合成 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 和助记符。由于可以有更多
每个助记符一个 WorkerKey
以上,您可能会看到 worker_max_instances
以上
特定助记符的日志文件。
对于 Android build,请参阅 “Android build 性能”页面。
实现永久性工作器
如需了解详情,请参阅创建永久性工作器页面 有关如何创建 worker 的信息。
以下示例展示了使用 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..." },
],
}
worker 会在 stdin
上以换行符分隔的 JSON 格式接收此内容(因为
requires-worker-protocol
设置为 JSON)。然后,Worker 会执行操作,
并将 JSON 格式的 WorkResponse
发送到其 stdout 上的 Bazel。然后运行 Bazel
解析此响应并手动将其转换为 WorkResponse
proto。接收者
使用二进制编码的 protobuf 与关联的 Worker 进行通信,而不是
JSON,requires-worker-protocol
将设置为 proto
,如下所示:
execution_requirements = {
"supports-workers" : "1" ,
"requires-worker-protocol" : "proto"
}
如果您没有在执行要求中添加 requires-worker-protocol
,
Bazel 会将工作器通信默认使用 protobuf。
Bazel 会从助记符和共享标志派生 WorkerKey
,因此如果
更改 max_mem
参数,则单独的工作器会
每个使用的值。如果存在以下情况,可能会导致内存消耗过多:
使用的变体过多。
每个工作器目前一次只能处理一个请求。实验性 多路复用工作器功能允许使用多个 如果底层工具是多线程的,并且封装容器设置为 理解这一点
在 此 GitHub 代码库 您可以看到使用 Java 和 Python 编写的示例工作器封装容器。如果您 使用 JavaScript 或 TypeScript 进行开发, @bazel/worker 软件包 和 nodejs worker 示例 可能有帮助。
工作器如何影响沙盒?
默认情况下,使用 worker
策略不会在
sandbox,类似于 local
策略。您可以将
--worker_sandboxing
标志,用于在沙盒内运行所有工作器,确保每个工作器
工具执行时,只能看到应该包含的输入文件。工具
可能仍会在内部请求之间泄露信息,例如通过
缓存。使用“dynamic
”策略
需要对 worker 进行沙盒化处理。
为了让工作器能够正确使用编译器缓存,系统会一并传递摘要 输出结果。因此,编译器或封装容器可以检查输入是否 也无需读取文件。
即使使用输入摘要来防止不必要的缓存, 提供的沙盒不如纯沙盒,因为该工具可能会 保留受先前请求影响的其他内部状态。
只有当 worker 实现支持多重工作器时,才能对其进行沙盒化。
并且必须通过
--experimental_worker_multiplex_sandboxing
标志。如需了解更多详情,请访问
设计文档)。
深入阅读
如需详细了解永久性工作器,请参阅:
- 原始永久性工作器博文
- Haskell 实现说明 {: .external}
- Mike Morearty 的博文 {: .external}
- 使用 Bazel 进行前端开发:Angular/TypeScript 和永久性工作器 与 Asana 合作 {: .external}
- Bazel 策略说明 {: .external}
- 在 bazel-discuss 邮寄名单中讨论信息丰富的员工策略 {: .external}