通用构建分发:颠覆性变革还是营销噱头?

目录

引言

远程和分布式构建模式一文解释了远程构建和分布式构建之间的区别以及它们的变体。具体来说,我们区分了“测试分发”和“通用构建分发”。

本文从更广阔的视角讨论分布式构建,以改善构建反馈时间。我们将首先解释工程师倾向于进行的更改类型,确定典型的瓶颈,并分享这些与分布式构建的关系。我们还将研究通用构建分发的性能潜力。最后,我们将探讨改善构建反馈时间的整体方法。

以下我们将更详细地阐述这三个发现

  • 分布式构建不能替代调优良好的构建过程。
  • 改进增量构建性能,而不是“完全重建”,是改善本地开发者体验最重要的方面。
  • 对于大多数JVM项目而言,除了测试分发之外,对调优良好的构建进行通用构建分发是一个渐进而非革命性的过程,它带来的性能优势微乎其微。

本文提出的分析和发现尤其适用于JVM生态系统项目。未来的后续文章将探讨Android和原生/iOS生态系统。

最重要的优化场景 #

改善本地开发者体验的两个关键在于理解工程师面临的典型瓶颈,以及工程师在添加新功能、修复错误和编写测试时所构建的更改类型。

测试执行是瓶颈 #

测试执行常常是构建时间中耗时最长的部分。优化构建以避免不必要的测试执行可以大幅提高生产力。Gradle构建工具在类路径上未检测到有意义的更改时已能跳过测试,并且还可以从构建缓存中恢复测试执行结果。停止重新运行您的测试一文很好地解释了通过最小化测试重新执行可获得的效率。

在分布式构建的背景下,此瓶颈通过现代测试分发解决,例如Gradle Enterprise提供的测试分发解决方案。

增量构建与完全重建的频率 #

下一个关键点是,在绝大多数情况下,工程师都在构建小的增量更改。我们认为,这些小的增量更改不太可能从超出测试分发的构建步骤分发中受益。此外,使用现代构建系统的开发人员很少在没有共享构建缓存或同一机器上保留先前构建历史记录的情况下执行“完全重建”。

考虑对Java类私有方法的正文进行更改:只有该类必须重新编译,并且包含它的库需要重新组装。但没有理由重新编译该库的下游使用者,因为它无法链接到私有方法。另一方面,考虑修改多项目构建中许多其他子项目使用的“通用”库的公共API。这将导致“多米诺骨牌效应”,使下游使用者重新编译。通用构建分发可能在此场景中有所帮助,但我们认为这是例外,而非常态(有关更多见解,请参阅下面的并行化因子)。

此外,Java编译相对于“原生”语言而言相对较快,这进一步降低了通用构建分发在Java项目中的优化潜力。

因此,我们对“从头开始”构建大型项目作为衡量构建系统性能的真实标准,或作为实施远程或分布式构建的理由的主张持怀疑态度。

并行化因子 #

理解任何构建(本地构建、远程托管或分布式)最大速度潜力的关键在于可视化其输出的相互依赖性。想象一个相对较小的软件项目,包含三个子项目:A、B和C。如果编译子项目C需要子项目A和B的输出,那么C依赖于A和B。最重要的是,我们不能在A和B都完成后才开始构建C;因此,最佳情况下的构建时间可以表示为max(A, B) + C。给定一个具有无限CPU核心的本地或远程构建主机,或一个无限分布式构建代理池,构建不能比这个瓶颈进一步并行化。

Example project structure 图1:项目结构

由于我们看到这个瓶颈是基于依赖性而非性能的,我们现在能够预测远程或分布式构建的潜在好处。

为了验证这一理论,我们与一些合作伙伴合作,对Gradle本身的构建和其他大型构建2进行了1一些分析,以确定在上述瓶颈下理论上可实现的最小构建时间。我们发现了这些有趣的发现:

  • 测试执行消耗了绝大部分构建时间,占端到端CI周期的80-90%。
  • 在测试执行之后,最耗时的任务是CPU密集型任务,如编译或验证,其次是磁盘密集型打包/组装任务。
  • 超过一半的非测试任务在单个进程中执行。

最后一点至关重要:作为单个进程运行的任务——没有其他进程同时执行——表明存在瓶颈,例如上面示例中的子项目C。单进程任务证明无法通过分发进行进一步优化。更强大的远程CPU可能更快地完成编译任务,但这种好处很容易被来回发送数据的开销抵消。

Cumulative work time
图2:累计工作时间,按并发工作者数量分组。一半的工作在没有其他繁忙进程并行运行的情况下执行。

抛开测试执行(通过测试分发解决,参见上文),并专注于构建时间中剩余的10-20%的CPU密集型部分,我们发现优化潜力很低。这些任务中有一半未能与其他进程并行执行,这意味着,充其量,通用分发解决方案只能加速总体构建时间的5-10%,同时产生构建复杂性和管理开销方面的显著成本。

前进之路 #

如上所述,开发人员所做的大部分更改都是小的增量更改,而最大的瓶颈通常是测试执行。因此,将构建优化集中在这些方面通常会获得最佳结果。以下部分列出了您的构建过程今天可以实施的一些关键步骤。这些构建性能优化的基本要素不仅会改善任何构建,无论是本地、远程还是分布式,而且还将确保未来潜在地迁移到远程或分布式环境时获得最佳性能。

按此顺序,我们建议利用这些Gradle构建工具功能来优化本地构建反馈时间。其中大部分功能在提高Gradle构建性能中都有更详细的文档说明。

  1. 增量构建
  2. 编译规避增量编译
  3. 远程构建缓存
  4. 并行执行
  5. 配置缓存(也增加了本地并行性)

此外,Gradle Enterprise中的以下功能大大缩短了测试反馈时间,这通常是构建性能中迄今为止最大的瓶颈

虽然通用构建分发在单独衡量时可能会显示出令人印象深刻的构建性能提升,但我们已经证明,对于大多数JVM项目来说,在经过良好优化的构建中,它不太可能为典型场景提供显著的额外构建性能改进。这并不是说我们认为通用分发解决方案不令人感兴趣。相反,我们将其视为我们长期路线图上的一个演进性而非革命性的解决方案。

总结 #

传闻证据和行业经验表明了两件事:首先,工程师最有可能迭代和重建小的增量更改——而不是从头开始重建整个项目。其次,无论构建更改的类型如何,测试执行是导致构建缓慢和降低开发人员生产力的主要原因。

通过使用活动进程计数作为本地构建潜在并行化的代理,我们已经表明,对于JVM生态系统中的许多构建,通用构建分发解决方案对构建性能的影响相对较小(如果有的话)。

以纯分布式方式运行JVM构建的所有方面并非万能药。现有的Gradle构建工具功能,如增量任务执行、编译规避、增量编译、构建缓存和配置缓存,今天即可使用,并且极大地缩短了构建时间,特别是对于最频繁的增量更改。此外,Gradle Enterprise中的商业功能,如测试分发预测性测试选择,极大地缩短了测试执行时间,这是大多数构建的主要瓶颈。

反馈 #

如果您有任何问题,请在我们的论坛Gradle 社区 Slack上告知我们。


  1. 我们使用这个工具从选定的Gradle Enterprise服务器池中收集了30天的Build Scan™数据。 

  2. 这些服务器捕获了Gradle、Gradle Enterprise商业产品、Spring项目以及一家使用Gradle构建数千个微服务的公司的构建数据。 

讨论