“动态执行”是 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 助记符的默认值。远程版本的运作方式也是如此。这两个 flag 可以多次指定。如果某个操作不能在本地执行,则可以照常远程执行,反之亦然。
如果您的远程系统有缓存,--dynamic_local_execution_delay
标记会在远程系统指示缓存命中后,向本地执行增加一个延迟(以毫秒为单位)。这样可以避免在可能存在更多缓存命中时运行本地执行。默认值为 1000 毫秒,但应调整为比缓存命中通常更长的时间。实际时间取决于远程系统和往返所用时间。通常情况下,对于给定远程系统的所有用户,该值将相同,除非其中一些用户距离很远而无法添加往返延迟时间。您可以使用 Bazel 性能剖析功能来查看典型的缓存命中需要多长时间。
动态执行可与本地沙盒策略和永久性工作器结合使用。与动态执行功能结合使用时,持久性工作器将会自动与沙盒一起运行,并且不得使用多重工作器。在 Darwin 和 Windows 系统中,沙盒化策略的执行速度可能较慢;您可以传递 --reuse_sandbox_directories
,以减少在这些系统上创建沙盒的开销。
动态执行也可以使用 standalone
策略运行,不过因为 standalone
策略在开始执行时必须获得输出锁定,因而有效地阻止了远程策略先完成。--experimental_local_lockfree_output
标志允许本地执行直接写入输出,但被远程执行中止(如果先完成),则可以解决这个问题。
如果动态执行过程中有一个分支优先运行但运行失败,则整个操作都会失败。这是一种故意选择的做法,可防止本地执行和远程执行之间的差异注意到。
如需进一步了解动态执行及其锁定的工作原理,请参阅 Julio Merer 的精彩博文
何时应使用动态执行功能?
动态执行需要某种形式的远程执行系统。目前无法使用仅缓存的远程系统,因为缓存未命中会被视为失败操作。
并非所有类型的操作都非常适合远程执行。最好的选择是原本就在本地更快的代理,例如通过使用永久性工作器的集群或运行速度足够快的远程工作器,其执行时间会占据主要位置。由于每个本地执行的操作都会锁定一定数量的 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 时,当有大量操作正在等待调度时,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,请参阅上文),但移除了一些可能的故障原因。