动态执行 是 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 标志
在远程系统
指示缓存命中后,会向本地执行添加以毫秒为单位的延迟。这样可以避免在可能发生更多缓存命中时运行本地执行
。默认值为 1000 毫秒,但应调整为比缓存命中通常所需的时间稍长。实际时间取决于远程
系统和往返所需的时间。通常,对于给定远程系统的所有用户,该值都是相同的
,除非其中一些用户距离足够远
,会增加往返延迟。您可以使用 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,请参阅上文),但会消除一些可能导致失败的原因。