持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第12天,点击查看活动详情
了解开发人员清楚底层硬件的重要性,并了解某些行业如何从具有可预测延迟的快速软件中受益。
令人惊讶的是,很少有拥有 10 年以上经验的 Java 开发人员对底层硬件有深入的了解。Java 最初的目标是“编写一次,随处运行”,但这是否意味着我们不应该关注硬件?Java 在将开发人员与他们的平台隔离开来方面做得非常出色。大多数开发人员宁愿解决他们的业务问题也不愿关心他们的芯片的内部工作负担是否过重,这并非没有道理。但是,某些行业(例如金融科技)受益于具有可预测延迟的快速软件,其中每一微秒都会对其利润产生巨大影响。对于这些领域,合理掌握底层硬件可能是有益的。
线程调度器
许多流行的 Java 框架通过将其工作负载分布在多个并发计算单元上来提供水平可扩展性。如果并发线程数超过内核数,则每次换出线程以运行另一个进程时,应用程序都会遭受性能下降。这种交换的性能成本通常会在总处理时间上增加数十个不必要的微秒,更不用说用于处理其他可能不那么关键的任务的时间量了。即使关键线程的数量不超过核心的数量,线程仍然会不时被换出或取消调度,以允许其他线程暂时运行。此外,此外,即使关键线程在100%CPU下运行,即使没有来自其他线程的明显压力,调度程序仍会偶尔将其移动到另一个空闲核心。这样的移动不仅会增加交换机本身的成本,还会由于缓存失效而导致抖动增加。
为了控制线程不被调度,我们可以将每个关键线程固定到一个内核。与内核隔离结合使用以减少其他任务的中断,这在很大程度上消除了由调度程序中断和内核迁移引起的抖动,并最大限度地利用指令和数据缓存的好处。
核心到核心延迟
一个经常被忽视的考虑因素是核心到核心的延迟。如果我们使用上述技术将线程固定到核心,那么考虑将哪些线程固定到哪些核心也很重要。
例如,如果我们在同一个插槽上的两个内核之间交换数据,那么在 Intel Xeon Gold 6346 上大约需要 100 纳秒。如果相反,我们在不同插槽上的内核之间交换数据,这将需要大约 225 纳秒。下面的第一个热图显示了内核之间交换数据所需的时间(以纳秒为单位),清楚地反映了两个内核在同一个套接字上时与套接字之间交换数据时的性能差异。
AMD 架构与 Intel 的不同之处在于引入了核心复合体 (CCX),CCX 是每个插槽内共享 L3 缓存的核心子集(与 Intel 的插槽范围 L3 缓存设计相比)。CCX 中的内核数量取决于特定的 CPU,常见配置为 2、4 和 8。这意味着现在的性能差异取决于线程在套接字内以及套接字之间的位置,在同一个 CCX 内的内核之间交换数据时可以实现最佳性能,这在第二个和第三个heatmaps中可以清楚地看到下面分别使用 4 核和 2 核 CCX。
Heatmaps
下图显示了在运行在不同内核上的一对线程之间交换数据时的延迟,颜色编码为最低数字绿色,从黄色变为红色以获得最高数字。所有三个热图都使用相同的比例。
Intel Xeon Gold 6346 (2x16 core)
AMD EPYC 7343 (2x16 core, 4-core CCX)
AMD EPYC 73F3 (2x16 core, 2-core CCX)
结论
在已将线程固定到独立内核的已优化 Linux 环境中,AMD 在同一 CCX 内的内核之间可获得最佳的内核到内核性能。在同一插槽中但跨 CCX 组的内核之间交换数据时,英特尔在这些测试中的性能优于 AMD 大约 2 倍。在套接字之间交换数据时,英特尔的性能也比 AMD 好。
鉴于此,当固定多个适合 CCX 的线程时,AMD 可以为分布式应用程序提供卓越的性能(召回 CCX 大小因 CPU 选择而异)。然而,如果工作负载分布在大量线程上,那么英特尔可能是首选平台,因为它具有较低的、插槽范围的内核到内核延迟。鉴于更一致的套接字范围性能,英特尔也可能成为更灵活的平台,以适应进程/线程数量的扩展。
小核心数 CCX 架构的另一个考虑因素是同一 CCX 内的核心之间的热管理。具体来说,CCX 设计要求关键线程必须放置在同一个 CCX 中以获得最佳性能,但在某些情况下,来自相邻内核的相对较高的热密度可能会导致性能受到限制。相比之下,英特尔的设计允许工作负载更多地分布在整个插槽中,这有助于控制热密度并提供更多的热管理灵活性。
其他操作系统(例如 macOS)在放置线程时不提供与 Linux 完全相同级别的细粒度控制,而且我们还没有启动 mac M1 ultra,但是当我们这样做时,看看它是如何实现的会很有趣比较。