动态执行

从 0.21 版开始动态执行是 Bazel 中的一项功能,可让同一操作在本地和远程执行相同的操作,使用已完成的第一个分支的输出,取消另一个分支。它将远程构建系统的执行能力和/或大型共享缓存与本地执行的低延迟结合在一起,为干净构建和增量构建提供两全其美的体验。

本页介绍了如何启用、调整和调试动态执行。如果您同时设置了本地和远程执行,并正在尝试调整 Bazel 设置以提高性能,此页面正适合您。如果您尚未设置远程执行,请先转到 Bazel 远程执行概览

启用动态执行功能?

动态执行模块是 Bazel 的一部分,但要使用动态执行模块,您必须已能够通过相同的 Bazel 设置在本地和远程进行编译。

如需启用动态执行模块,请将 --internal_spawn_scheduler 标志传递给 Bazel。这将添加一个名为 dynamic 的新执行策略。现在,您可以将此用作要动态运行的助记符的策略,例如 --strategy=Javac=dynamic。如需了解如何选择要为哪些助记符启用动态执行,请参阅下一部分。

对于使用动态策略的任何助记符,远程执行策略取自 --dynamic_remote_strategy 标志,本地策略取自 --dynamic_local_strategy 标志。传递 --dynamic_local_strategy=worker,sandboxed 会为动态执行的本地分支设置默认值,以尝试按此顺序使用 worker 或沙盒化执行。传递 --dynamic_local_strategy=Javac=worker 只会替换 Javac 助记符的默认值。远程版本的运作方式与此相同。您可以多次指定这两个标志。如果某项操作无法在本地执行,可以照常远程执行,反之亦然。

如果您的远程系统具有缓存,--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 线程上完成工作。这是因为非 worker 助记符占用了足够的资源来延迟 worker。

以更好的动态执行性能来分析数据

当只有 Javac 在运行时采用动态执行时,只有大约一半已启动的 worker 在开始其工作后最终会失败。

之前推荐的 --experimental_spawn_scheduler 标志已废弃。它会开启动态执行功能,并将 dynamic 设为所有助记方法的默认策略,这通常会导致此类问题。

问题排查

动态执行的问题可能很细微并且难以调试,因为只有在本地和远程执行的某些特定组合下才会出现。--debug_spawn_scheduler 添加了来自动态执行系统的额外输出,有助于调试这些问题。您还可以调整 --local_execution_delay 标志以及远程作业与本地作业的数量,以便更轻松地重现问题。

如果您在使用 standalone 策略进行动态执行时遇到问题,请尝试在不使用 --experimental_local_lockfree_output 的情况下运行,或在沙盒化模式下运行本地操作。这可能会略微减慢您的构建速度(如果您使用的是 Mac 或 Windows,请参阅上文),但会移除一些可能导致构建失败的原因。