一般构建分布:改变游戏规则还是一个噱头?

122 阅读8分钟

远程和分布式构建模式一文解释了远程和分布式构建的区别以及各自的变化。具体来说,我们区分了 "测试分布 "和 "一般构建分布"。

这篇文章从改善构建反馈时间的更广泛的角度来讨论分布式构建。我们将首先解释工程师们倾向于做的变化类型,确定典型的瓶颈,并分享这些与分布式构建的关系。我们还将研究一般构建分布的性能潜力。最后,我们将探索一种改善构建反馈时间的整体方法。

在下文中,我们将更详细地阐述这三个发现。

  • 以分布式的方式进行构建,并不能替代精心调整的构建过程。
  • 提高增量构建性能,而不是 "完全重建",是改善本地开发者体验的最重要方面。
  • 超越测试分布的一般构建分布是一个进化的过程,而不是革命的过程,对于大多数JVM项目来说,产生的性能优势是微不足道的。

这里介绍的分析和发现特别适用于JVM生态系统的项目。未来的后续文章将讨论Android和本地/iOS生态系统。

需要优化的最重要场景

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

测试执行是瓶颈

测试执行经常是构建时间中最耗时的部分。优化构建以避免不必要的测试执行可以产生巨大的生产力。当在classpath上没有检测到有意义的变化时,Gradle构建工具已经跳过了测试,并且还可以从构建缓存中恢复测试执行结果。Stop rerunning your tests一文很好地解释了通过尽量减少测试的重新执行所带来的效率。

在分布式构建的背景下,这个瓶颈可以通过现代测试分布来解决,比如Gradle Enterprise提供的测试分布解决方案。

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

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

考虑到对一个Java类的私有方法主体的改变:只有该类必须被重新编译,包含它的库被重新组装。但是没有理由重新编译该库的下游用户,因为它不能连接到一个私有方法。在光谱的另一端,考虑对一个 "通用 "库的公共API进行修改,该库在多项目构建中被许多其他子项目所使用。这将导致 "多米诺骨牌效应",使其下游的消费者被重新编译。在这种情况下,一般的构建分布可能会有帮助,但我们认为这是例外,而不是常态(更多的见解见下面的平行化因素)。

此外,与 "本地 "语言相比,Java的编译速度相对较快,进一步降低了Java项目中通用构建分布的优化潜力。

因此,我们鼓励大家对 "从头开始 "构建大型项目的说法持怀疑态度,认为这是衡量构建系统性能的真正标准,或作为实施远程或分布式构建的理由。

平行化系数

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

image.png

图1:项目结构

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

为了对这一理论进行检验,我们对并行化因素进行了一些分析1,以建立一个理论上可实现的最小构建时间,考虑到上述的瓶颈。我们与一些合作伙伴合作,检查了Gradle本身的构建和其他有规模的构建2。我们发现了这些有趣的结果。

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

这最后一点很关键:作为单一进程运行的任务--没有其他进程同时执行--表明存在瓶颈,如上例中的子项目C。单一进程的任务证明,通过分配进一步优化是不可能的。更强大的远程CPU可能更快完成编译任务,但这种好处很容易被来回发送比特的开销所抵消。

image.png

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

抛开测试执行(由测试分布解决,见上文),专注于构建时间中剩余的CPU密集型10-20%部分,我们发现优化的潜力很低。这些任务中有一半未能与其他进程并行执行,这意味着一般的分布式解决方案最多只能加快整个构建时间的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构建数千个微服务的公司的构建数据