为何我们将Timefold Java工作节点从AMD迁移到ARM64
当我们调查平台上 Timefold Solver 工作节点出现意外的性能波动时,最终将原因锁定在 AMD(x86-64)云实例的超线程技术上。切换到 ARM(ARM64)后,每个 vCPU 直接映射到一个物理核心,问题随之消失。对于并发求解器工作负载,每美元的有效吞吐量大约提升了一倍。
本文最初由 Timefold 工程师 Lars Beckmann、Jenne De Bleser 和 Lukáš Petrovický 撰写。
由 Tom Cools 改编后发布于 foojay。
有些不对劲。
我们使用 Timefold Solver 来驱动我们的优化平台,这是一个处理排班、车辆路径等问题的 Java 库。在我们的平台上,它运行在 Quarkus 之上。
我们的一个客户将同一个优化过程运行了两次。相同的输入,相同的配置。唯一的区别是换了一个约束权重,将优化目标从旅行时间改为旅行距离。按理说这不应该影响性能。然而,第二次运行的移动计算速度(我们的关键性能指标)还不到第一次的一半。
一半。仅仅因为一个配置调整。
这种异常现象让人停下来思考:为什么?
性能差距的原因是什么?
我们的第一反应是检查发送到平台的数据是否存在差异。在仔细检查输入后,我们发现了一些配置噪声,但即使在控制这些因素后,用完全相同的设置运行这两个任务,差距依然存在。
一次运行始终是另一次的两倍快。
Timefold Solver 是确定性的。给定相同的 CPU 时间,它会走完全相同的路径。因此,如果你在相同输入下看到不同的移动计算速度,原因不在求解器,而在底层机器。
罪魁祸首:超线程
我们发现了以下情况:在 GCP 上运行求解器工作负载的节点是基于 AMD 的机器,拥有 8 个物理核心和 16 个 vCPU。“额外”的核心来自超线程——Intel 和 AMD 的一项技术,让两个指令流在同一物理核心上同时运行,共享执行单元、缓存和内存带宽。
理论上,超线程通过让核心更忙碌来提高吞吐量。但实际上,对于像我们的求解器这样计算密集型的工作负载,它是一个陷阱。
当两个求解器任务落在同一个节点上时,它们会争夺相同的物理核心。每个任务都认为自己拥有一个完整的 vCPU,但实际上是在与另一个任务分时共享一个真实的核心。它们在 L1/L2 缓存、执行端口和内存带宽上互相竞争。两个工作负载的性能都会断崖式下跌。
这正是我们观察到的现象。我们的一位工程师进行了受控基准测试:相同的优化问题、相同的节点类型,改变并发求解器任务的数量。结果令人震惊。
- AMD(8 核心 / 16 vCPU):一次运行一个任务时性能稳定且快速。加入第二个任务后,性能几乎减半。继续增加任务,性能下降得比预期快得多——如果瓶颈仅仅是“每个任务得到的 CPU 时间更少”,下降速度不应该这么快。
- ARM(16 个物理核心 / 16 个 vCPU):性能随着负载增加而优雅地下降。在 16 个并发任务的情况下,每个任务的运行速度大致符合预期——约为可用计算资源的 1/16。
[LOADING...]
差异归结于一个有意的架构选择:云实例中使用的 ARM 处理器(无论是 GCP 的 Tau 系列搭载 Ampere Altra,还是 AWS Graviton)将每个 vCPU 映射到一个物理核心。没有超线程。你分配了多少,就得到多少。
对于计算密集型、单线程的工作负载,这是一个有据可查的特性(不过谁会读文档呢?)。当工作负载需要核心的全部资源——寄存器、缓存、执行单元——时,超线程就会从资产变成负担。
为什么 ARM 在成本上同样胜出
还有另一个角度坚定了我们的选择:成本。
一个 AMD(x86-64)节点拥有 8 个物理核心和 16 个 vCPU,但考虑到并发负载下的性能损失,它实际上不允许你运行 16 个高 CPU 负载的工作负载。为了维持同样的吞吐量,你需要减少并发度,本质上就是把一台 16 vCPU 的机器当作小得多的机器来用。团队中的一位工程师指出,在达到性能断崖之前,有效利用率可能低至每个 vCPU 仅 0.5 个工作负载。
与此同时,等效的 ARM(ARM64)节点(16 个物理核心、16 个 vCPU)可以干净地处理 16 个并发任务。而且它的定价也很有竞争力。一旦你考虑到 AMD 在负载下的利用率现实,ARM 对于此类工作负载的成本效益大约高出两倍。
这与整个行业的发现是一致的。AWS 的 自有基准测试 和独立测试都反复表明,对于计算密集型工作负载,ARM 在价格性能比上有显著提升。GCP 自己的 ARM 产品 也显示出类似模式。
超线程不是你的朋友
如果你在云中运行计算密集型工作负载——优化求解器、仿真引擎、密集的数据处理循环——在并发负载下,超线程不是你的朋友。它制造了你拥有比实际更多核心的假象,而这种假象的代价在你节点真正繁忙时付出——这是最糟糕的时机。
ARM 云实例通过将 vCPU 映射到物理核心来解决这个问题。结果是更可预测的性能、更好的利用率,并且在大多数配置下更低的成本。
如果你还没有使用 ARM64 vCPU,并且你的 Java 应用程序出现了奇怪性能下降,这很可能就是原因所在。
我们已全面迁移到 ARM。我们不打算回头。
本文《Why We Moved Our Timefold Java Worker Pods from AMD to ARM64》最初发表于 foojay。