英特尔 OpenMP 库通过设置 KMP_AFFINITY=scatter 显着降低了 AMD 平台上的内存带宽

Intel OpenMP library slows down memory bandwidth significantly on AMD platforms by setting KMP_AFFINITY=scatter

对于内存受限的程序,使用多个线程并不总是更快,比如与内核数量相同,因为线程可能会竞争内存通道。通常在双路机器上,线程越少越好,但我们需要设置亲和性策略,将线程分布在多个插槽上,以最大化内存带宽。

Intel OpenMP声称KMP_AFFINITY=scatter就是为了达到这个目的,相反的值“compact”是让线程尽可能靠近。我已经使用 ICC 构建了用于基准测试的 Stream 程序,这一说法很容易在 Intel 机器上得到验证。如果设置了 OMP_PROC_BIND,则会忽略 OMP_PLACES 和 OMP_PROC_BIND 等本机 OpenMP 环境变量。你会得到这样的警告:

        OMP: Warning #181: OMP_PROC_BIND: ignored because KMP_AFFINITY has been defined

然而,我获得的最新 AMD EPYC 机器的基准测试显示了非常奇怪的结果。 KMP_AFFINITY=scatter 提供可能的最慢内存带宽。似乎这个设置在 AMD 机器上做的恰恰相反:将线程放置得尽可能近,以至于甚至每个 NUMA 节点的 L3 缓存都没有得到充分利用。如果我明确设置 OMP_PROC_BIND=spread,它会被 Intel OpenMP 忽略,如上面的警告所述。

AMD 机器有两个插槽,每个插槽有 64 个物理内核。我已经使用 128、64 和 32 个线程进行了测试,我希望它们分布在整个系统中。使用 OMP_PROC_BIND=spread,Stream 给我的三元组速度分别为 225、290 和 300 GB/s。但是一旦我设置 KMP_AFFINITY=scatter,即使 OMP_PROC_BIND=spread 仍然存在,Streams 也会给出 264、144 和 72 GB/s.

请注意,对于 128 个内核上的 128 个线程,设置 KMP_AFFINITY=scatter 可提供更好的性能,这进一步表明实际上所有线程都尽可能靠近放置,但根本没有分散。

总而言之,KMP_AFFINITY=scatter 在 AMD 机器上显示完全相反的(以糟糕的方式)行为,它甚至会覆盖本机 OpenMP 环境,而不管 CPU品牌。整个情况听起来有点可疑,因为众所周知 ICC 检测到 CPU 品牌并使用 MKL 中的 CPU 调度程序在非英特尔机器上启动较慢的代码。那么,如果 ICC 检测到非英特尔 CPU,为什么不能简单地禁用 KMP_AFFINITY 并恢复 OMP_PROC_BIND?

这是某人的已知问题吗?或者有人可以验证我的发现?

为了提供更多上下文,我是商业计算流体动力学程序的开发人员,不幸的是我们将我们的程序与 ICC OpenMP 库链接并且默认设置 KMP_AFFINITY=scatter 因为在 CFD 中我们必须解决大 -缩放稀疏线性系统,这部分非常受内存限制。我发现通过设置 KMP_AFFINITY=scatter,我们的程序变得比程序在 AMD 机器上可以达到的实际速度慢 4 倍(当使用 32 个线程时)。

更新:

现在使用 hwloc-ps 我可以确认 KMP_AFFINITY=scatter 实际上在我的 AMD threadripper 3 机器上进行了“压缩”。我附上了 lstopo 结果。我 运行 我的 CFD 程序(由 ICC2017 构建)有 16 个线程。 OPM_PROC_BIND=spread可以在每个CCX中放置一个线程,从而充分利用L3缓存。 Hwloc-ps -l -t 给出:

设置KMP_AFFINITY=scatter时,得到

我会尝试最新的 ICC/Clang OpenMP 运行时间,看看它是如何工作的。

TL;DR:不要使用 KMP_AFFINITY。它不是便携式的。首选OMP_PROC_BIND(不能与KMP_AFFINITY同时使用)。您可以将它与 OMP_PLACES 混合使用,以手动将线程绑定到内核。此外,numactl 应该用于控制内存通道绑定或更一般的 NUMA 效果。

长答案:

线程绑定OMP_PLACES可用于将每个线程绑定到特定核心(减少上下文切换和 NUMA 问题)。 OMP_PROC_BINDKMP_AFFINITY 理论上应该正确地做到这一点,但实际上,它们在某些系统上无法做到这一点。请注意 OMP_PROC_BINDKMP_AFFINITY 是独占选项:它们应该 而不是 一起使用(OMP_PROC_BIND 是旧 [=10 的新便携式替代品=] 环境变量)。随着核心拓扑从一台机器更改为另一台机器,您可以使用 hwloc 工具获取 OMP_PLACES 所需的 PU id 列表。尤其是 hwloc-calc 获取列表和 hwloc-ls 检查 CPU 拓扑。所有线程都应单独绑定,以便无法移动。您可以使用 hwloc-ps.

检查线程的绑定

NUMA 效果:AMD 处理器是通过组装多个 CCX 并通过 high-bandwidth 连接(AMD Infinity Fabric ).因此,AMD 处理器是 NUMA 系统。如果不考虑,NUMA 效应可能会导致性能显着下降。 numactl 工具旨在 control/mitigate NUMA 效果:进程可以使用 --membind 选项绑定到内存通道,内存分配策略可以设置为 --interleave(或 --localalloc 如果进程是 NUMA-aware)。理想情况下,processes/threads 应该只对分配的数据起作用,first-touched 在它们的本地内存通道上。如果您想在给定的 CCX 上测试配置,您可以使用 --physcpubind--cpunodebind.

我的猜测是 Intel/Clang 运行time 在设置 KMP_AFFINITY=scatter 时没有执行良好的线程绑定,因为 错误的 PU 映射(可能来自 OS 错误、运行 时间错误或错误的 user/admin 设置)。可能是因为 CCX(因为包含多个 NUMA 节点的主流处理器非常罕见)。
在 AMD 处理器上,由于数据通过 (quite-slow) Infinity Fabric 互连 移动并且可能由于其 saturation 以及记忆通道之一。我建议您不要相信 OpenMP 运行time 的自动线程绑定(使用 OMP_PROC_BIND=TRUE),而是手动执行 thread/memory 绑定,然后在需要时报告错误。

下面是生成命令行的示例,以便 运行 您的应用程序: numactl --localalloc OMP_PROC_BIND=TRUE OMP_PLACES="{0},{1},{2},{3},{4},{5},{6},{7}" ./app

PS:注意PU/core ID和logical/physical ID。