OpenMP在NUMA架构下的调优

网友投稿 1275 2022-05-30

OpenMP 和 NUMA架构

OpenMP是一个基于共享内存并发编程模型。在程序中加入了OpenMP引导语后,主线程会生成一系列的子线程,并将任务划分给子线程进行执行。这里需要强调的是,所有的线程在同一个地址空间内运行,每个线程有独立的栈和程序计数器,但是所有线程共享进程的堆、数据段、代码段等内存空间。

NUMA架构全称non-uniform memory achitecture,是一种非对称的内存访问架构。简单的说,不同的处理器访问同一块内存的时延不一样,或者说同一个处理器访问不同内存的时延不一样。鲲鹏920就基于这样的NUMA架构。在Linux命令行中执行numactl --hardware可以看到当前机器的NUMA拓扑结构:

[user@A191240619 temp]$ numactl --hardware available: 4 nodes (0-3) node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 node 0 size: 128124 MB node 0 free: 11256 MB node 1 cpus: 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 node 1 size: 129020 MB node 1 free: 2018 MB node 2 cpus: 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 node 2 size: 129020 MB node 2 free: 23937 MB node 3 cpus: 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 node 3 size: 129019 MB node 3 free: 18981 MB node distances: node 0 1 2 3 0: 10 16 32 33 1: 16 10 25 32 2: 32 25 10 16 3: 33 32 16 10

可以看到当前机器有4个NUMA节点,每个节点有128G内存和32个CPU,node distance代表了跨NUMA节点的访存延迟。一个CPU访问自己所在的NUMA节点延迟最小,访问其他节点则存在2-3倍的访存延迟。

这就带来一个问题,在NUMA架构运行OpenMP程序时,如何将某个线程所需的数据尽可能放在自己所在的NUMA节点,避免跨节点的内存访问?

数据初始化

OpenMP在NUMA架构下的调优

以如下代码为例:

int main() { double *A = new double[1000]; for (int i = 0; i < 1000; i++) A[i] = 0.0; #pragma omp for for (int i = 0; i < 1000; i++) A[i] = do_work(i, A); delete[] A; return 0; }

大部分的NUMA架构都是基于First touch的策略来分配内存,也就是内存会分配在第一次访问此内存的核所在节点上。在以上例子中,数据初始化的部分并没有加OpenMP的引导语,会由主线程单独执行。所以所有的A数组都会被放在一个单独的NUMA节点,而在后续并行计算部分,所有其他NUMA节点的线程访问此数据都会产生较高延迟。

因此,较为理想的情况是,需要将数据初始化的部分也并行化,从而将A数组较为平均地分配到不同的NUMA节点,减少跨NUMA节点访存延迟:

#pragma omp for for (int i = 0; i < 1000; i++) A[i] = 0.0;

线程绑核

同样基于以上思想,执行线程的CPU应该和执行线程所以依赖的数据尽可能放在一个NUMA节点。主要有两个环境变量决定线程在什么核上执行:

OMP_PLACES = [cores, threads, sockets] 决定线程在什么地方执行

OMP_PROC_BIND = [spread, close, master] 决定线程和OMP_PLACES如何映射

例如OMP_PLACES=cores决定每个线程绑核,OMP_PROC_BIND=close决定每个线程放在尽可能近得放在每个核上,例如主线程在CPU0上,则线程1放在CPU1上,线程2放在CPU2上…

以上调优思路适用于所有NUMA架构的OpenMP应用,但具体的参数选择需要根据具体的应用灵活特征灵活调整。

任务调度 高性能计算

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:两项安全权威认证,全球首家通过!
下一篇:《Java设计模式及实践》—2.5 对象池模式
相关文章