3

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

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,英特尔 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 调度程序在非 Intel 机器上启动较慢的代码。那么,如果 ICC 检测到非 Intel CPU,为什么不能简单地禁用 KMP_AFFINITY 并恢复 OMP_PROC_BIND 呢?

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

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

更新:

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

在此处输入图像描述

在设置 KMP_AFFINITY=scatter 时,我得到了

在此处输入图像描述

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

在此处输入图像描述

4

1 回答 1

3

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

长答案

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

NUMA 效果:AMD 处理器是通过将多个CCX与高带宽连接(AMD Infinity Fabric)连接在一起而构建的。因此,AMD 处理器是NUMA 系统。如果不考虑,NUMA 效果可能会导致性能显着下降。该numactl工具旨在控制/减轻 NUMA 影响:可以使用该--membind选项将进程绑定到内存通道,并且可以将内存分配策略设置为--interleave(或者--localalloc如果进程支持 NUMA)。理想情况下,进程/线程应该只处理在其本地内存通道上分配和首次接触的数据。如果您想在给定的 CCX 上测试配置,您可以使用--physcpubind--cpunodebind

KMP_AFFINITY=scatter我的猜测是,由于错误的 PU 映射(可能来自操作系统错误、运行时错误或错误的用户/管理员设置),在设置时 Intel/Clang 运行时不会执行良好的线程绑定。可能是由于 CCX(因为包含多个 NUMA 节点的主流处理器非常罕见)。
在 AMD 处理器上,访问另一个 CCX 内存的线程通常会支付额外的大量成本,因为数据通过(非常慢的)Infinity Fabric 互连移动,并且可能由于其饱和以及内存通道之一。我建议您不要相信 OpenMP 运行时的自动线程绑定(使用OMP_PROC_BIND=TRUE),而是手动执行线程/内存绑定,然后在需要时报告错误。

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

PS:注意 PU/核心 ID 和逻辑/物理 ID。

于 2020-10-18T15:35:09.687 回答