OpenMP在NUMA架构下的调优

举报
keyboard artist 发表于 2021/12/25 16:10:00 2021/12/25
【摘要】 OpenMP 和 NUMA架构OpenMP是一个基于共享内存的并发编程模型。在程序中加入了OpenMP引导语后,主线程会生成一系列的子线程,并将任务划分给子线程进行执行。这里需要强调的是,所有的线程在同一个地址空间内运行,每个线程有独立的栈和程序计数器,但是所有线程共享进程的堆、数据段、代码段等内存空间。NUMA架构全称non-uniform memory achitecture,是一种非...

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应用,但具体的参数选择需要根据具体的应用灵活特征灵活调整。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。