OpenMP在NUMA架构下的调优
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节点,避免跨节点的内存访问?
数据初始化
以如下代码为例:
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应用,但具体的参数选择需要根据具体的应用灵活特征灵活调整。
- 点赞
- 收藏
- 关注作者
评论(0)