了解面向三维同性有限差分 (3DFD) 波动方程代码的 NUMA

本文将介绍一些技巧,帮助软件开发人员识别并修复使用最新英特尔软件开发工具时遇到的与 NUMA 相关的应用性能问题。

快速链接

1. 简介

非一致性内存访问 (NUMA) 是一种用于多处理的计算机内存设计,在多处理中,内存访问时间取决于与处理器相关的内存位置。根据 NUMA,处理器访问本地内存的速度要快于访问远程内存(位于其他处理器上的内存或处理器之间共享的内存)。

本文将简要介绍如何使用英特尔® VTune™ Amplifier XE 的最新内存访问特性,以识别应用中与 NUMA 相关的问题。 英特尔® 开发人员专区 (IDZ) 曾发表了一篇文章,文章对比了运行于英特尔® 至强™ 处理器和英特尔® 至强融核™ 协处理器的同性三维有限差分应用的开发过程和性能,本文是该文章的进一步延伸。 我们还就源代码修改提出了几点建议,以便 NUMA 环境中的应用实现一致的卓越性能。

我们这里仅探讨面向英特尔® 至强™ 处理器优化的版本,以便着重解决 NUMA 问题。 如欲下载代码,请点击此处。就本文来说,我们将使用为 ISO3DFD 应用提供的源代码的 dev06 版本来对比重要指标,并了解在应用中引进 NUMA 感知的优势。

 

2. 编译和执行 ISO3DFD 应用的步骤

可使用以下 makefile1 编译应用:

make build version=dev06 simd=avx2

可使用和源一同提供的 run_on_xeon.pl 脚本运行该应用:

./run_on_xeon.pl executable_name n1 n2 n3 nb_iter n1_block \ 
 	n2_block n3_block kmp_affinity nb_threads  
 
where
        -executable_name: The executable name
        -n1: N1 //X-Dimension
         -n2: N2 //Y-Dimension
        -n3: N3   //Z-Dimension
        -nb_iter: The number of iterations
        -n1_block: size of the cache block in x dimension
        -n2_block: size of the cache block in y dimension
        -n3_block: size of the cache block in z dimension
        -kmp_affinity: The thread partitionning
        -nb_threads: The number of OpenMP threads

 

3. 识别与 NUMA 相关的性能问题

当代 NUMA 架构非常复杂。 研究内存访问之前,验证 NUMA 是否会影响应用性能将非常有帮助。 这一点可以通过 numactl2 实用程序来实现。 验证后,必须跟踪延迟较高的内存访问。 优化这些内存访问可以进一步提升性能。

3.1 numactl

如要确定应用是否会受到 NUMA 的影响,最快的一种方法是完全在单路/NUMA 节点之外运行该应用,然后将其与在多个 NUMA 节点上运行时的性能进行比较。在不受 NUMA 影响的理想场景中,应该可以在插槽范围内顺利扩展,而且,如果没有其他影响扩展的因素,单路性能应该能够提升两倍(相当于使用双路系统)。 如下所示,就 ISO3DFD 来说,单路性能要优于全节点,因此应用性能会受到 NUMA 的影响。尽管这种方法能够帮助我们确定 NUMA 是否会影响应用性能,但它无法帮助我们查明问题所在的确切位置。我们可以使用英特尔 VTune Amplifier XE 的内存访问分析特性详细研究 NUMA 问题。

- 未使用 numactl 时的双路性能(每路 22 个线程):

./run_on_xeon.pl bin/iso3dfd_dev06_cpu_avx2.exe 448 2016 1056 10 448 24 96 compact 44

n1=448 n2=2016 n3=1056 nreps=10 num_threads=44 HALF_LENGTH=8
n1_thrd_block=448 n2_thrd_block=24 n3_thrd_block=96
allocating prev, next and vel: total 10914.8 Mbytes
------------------------------
time:           3.25 sec
throughput:  2765.70 MPoints/s
flops:        168.71 GFlops

- 使用 numactl 时的单路性能:

numactl -m 0 -c 0 ./run_on_xeon.pl bin/iso3dfd_dev06_cpu_avx2.exe 448 2016 1056 10 448 24 96 compact 22

n1=448 n2=2016 n3=1056 nreps=10 num_threads=22 HALF_LENGTH=8
n1_thrd_block=448 n2_thrd_block=24 n3_thrd_block=96
allocating prev, next and vel: total 10914.8 Mbytes
-------------------------------
time:           3.05 sec
throughput:  2948.22 MPoints/s
flops:        179.84 GFlops

- 在系统上运行两条进程,每条进程锁定一个插槽(每路 22 个线程):

numactl -c 0 -m 0 ./run_on_xeon.pl bin/iso3dfd_dev06_cpu_avx2.exe \ 
	448 2016 1056 10 448 24 96 compact 22 & \
numactl -c 1 -m 1 ./run_on_xeon.pl bin/iso3dfd_dev06_cpu_avx2.exe \ 
	448 2016 1056 10 448 24 96 compact 22 &

n1=448 n2=2016 n3=1056 nreps=10 num_threads=22 HALF_LENGTH=8
n1_thrd_block=448 n2_thrd_block=24 n3_thrd_block=96
allocating prev, next and vel: total 10914.8 Mbytes
-------------------------------
time:           2.98 sec
throughput:  2996.78 MPoints/s
flops:        180.08 GFlops
n1=448 n2=2016 n3=1056 nreps=10 num_threads=22 HALF_LENGTH=8
n1_thrd_block=448 n2_thrd_block=24 n3_thrd_block=96
allocating prev, next and vel: total 10914.8 Mbytes
-------------------------------
time:           3.02 sec
throughput:  2951.22 MPoints/s
flops:        179.91 GFlops

 

3.2 使用英特尔® VTune™ Amplifier XE - 内存访问分析

在支持 NUMA 的处理器中,不仅需要研究运行中 CPU 的高速缓存失误,还要研究针对另一 CPU 的远程 DRAM 和高速缓存的引用。为了获取有关这些详情的洞察,我们在应用上运行内存访问分析,如下所示:

amplxe-cl -c memory-access –knob analyze-mem-objects=true \ 
     -knob mem-object-size-min-thres=1024  -data-limit=0 \ 
     -r ISO_dev06_MA_10 ./run_on_xeon.pl bin/iso3dfd_dev06_cpu_avx2.exe \
     448 2016 1056 10 448 24 96 compact 44

以下几个指标与 NUMA 相关:

3.2.1 Memory Bound – 该应用是否受内存限制? 如果受限,带宽利用率直方图是否显示较高的 DRAM 带宽利用率? 因为实际计算密集型工作在插槽之间平分,因此插槽之间的带宽利用率必须达到平衡。

我们可以在 Summary 窗口中确定应用是否受内存限制。

图 1: 内存受限指标和 DRAM 带宽直方图

请注意,Memory Bound 指标非常高,并且十分突出,而 DRAM bandwidth utilization 处于中低水平,这不是我们期望的结果,需要进一步调查研究。

3.2.2 英特尔® 快速通道互联(英特尔® QPI)带宽。应用性能有时还会受到插槽间英特尔 QPI 链路带宽的限制。 英特尔 VTune Amplifier 提供识别导致这类带宽问题的根源和内存对象的机制。

Summary 窗口中,使用 Bandwidth Utilization Histogram 并选择 Bandwidth Domain 下拉菜单中的 QPI

图 2:英特尔® 快速通道互联带宽利用率直方图。

您还可以切换至自下而上视图,并在时间线视图中选择 QPI 带宽利用率较高的区域,并通过该选择进行过滤。

图 3:带宽利用率时间线视图

使用过滤后,时间线图显示仅一个插槽使用了 DRAM 带宽,而 QPI 带宽高达 38 GB/秒。

在相同的自下而上视图中,时间线面板下方的网格显示了该时间范围内所执行的内容。为了查看导致高英特尔 QPI 流量的函数名称,我们从下拉菜单中选择分组至 Bandwidth Domain / Bandwidth Utilization Type / Function / Call Stack,然后展开 High 利用率的 QPI 带宽域。

图 4:高英特尔® 快速通道互联带宽 - 自下而上视网格图。

NUMA 机器在单个插槽上为 OpenMP* 线程分配内存,而线程分散至各个插槽,因此这些是此类机器遭遇的常见问题。 这样迫使部分线程通过英特尔 QPI 从远程 DRAM 或远程高速缓存加载数据,因此速度比访问本地内存慢得多。

 

4. 修改代码以减少远程内存访问

为了降低 NUMA 的影响,在插槽上运行的线程应该访问本地内存,从而降低英特尔 QPI 流量。这可以使用首次接触策略。在 Linux* 上,内存页面基于首次访问分配;即数据只有在首次写入时才在内存中以物理的形式映射。这样有利于接触数据的线程接近供其运行的 CPU。为达到该目的,必须使用相同的 OpenMP 循环命令将内存初始化成用于计算的内存。考虑到这点,src/dev06/iso-3dfd_main.cc 中的初始化函数(包含在 ISO3DFD 源代码中)需要替换成支持首次接触的 initialize_FT。结果,线程将很可能访问并初始化本地内存中用于 iso_3dfd_it 函数(传播计算密集型地震波)的相同数据块。此外,我们将静态 OpenMP 调度用于初始化和计算,以进一步提高性能。

void initialize_FT(float* ptr_prev, float* ptr_next, float* ptr_vel, Parameters* p, size_t nbytes, int n1_Tblock, int n2_Tblock, int n3_Tblock, int nThreads){

        #pragma omp parallel num_threads(nThreads) default(shared)
        {
                float *ptr_line_next, *ptr_line_prev, *ptr_line_vel;
                int n3End = p->n3;
                int n2End = p->n2;
                int n1End = p->n1;
                int ixEnd, iyEnd, izEnd;
                int dimn1n2 = p->n1 * p->n2;
                int n1 = p->n1;
                #pragma omp for schedule(static) collapse(3)
                for(int bz=0; bz<n3End; bz+=n3_Tblock){
                        for(int by=0; by<n2End; by+=n2_Tblock){
                                for(int bx=0; bx<n1End; bx+=n1_Tblock){
                                        izEnd = MIN(bz+n3_Tblock, n3End);
                                        iyEnd = MIN(by+n2_Tblock, n2End);
                                        ixEnd = MIN(n1_Tblock, n1End-bx);

                                        for(int iz=bz; iz<izEnd; iz++) {
                                                for(int iy=by; iy<iyEnd; iy++) {
                                                       ptr_line_next = &ptr_next[iz*dimn1n2 + iy*n1 + bx];
                                                        ptr_line_prev = &ptr_prev[iz*dimn1n2 + iy*n1 + bx];
                                                        ptr_line_vel = &ptr_vel[iz*dimn1n2 + iy*n1 + bx];

                                                        #pragma ivdep
                                                        for(int ix=0; ix<ixEnd; ix++) {

                                                                ptr_line_prev[ix] = 0.0f;
                                                                ptr_line_next[ix] = 0.0f;
                                                                ptr_line_vel[ix] = 2250000.0f*DT*DT;//Integration of the v² and dt² here
                                                        }
                                                }
                                        }
                                }
                        }
                }
        }
        
        float val = 1.f;
        for(int s=5; s>=0; s--){
                for(int i=p->n3/2-s; i<p->n3/2+s;i++){
                        for(int j=p->n2/4-s; j<p->n2/4+s;j++){
                                for(int k=p->n1/4-s; k<p->n1/4+s;k++){
                                        ptr_prev[i*p->n1*p->n2 + j*p->n1 + k] = val;
                                }
                        }
                }
                val *= 10;
       }
}

 

5. 针对修改版的内存访问分析

我们在此感兴趣的指标包括 DRAM 带宽利用率和 QPI 带宽。

图 5:内存受限指标和 DRAM 带宽利用率 - 修改版。

通过 Summary 窗口我们可以看出,应用仍然受限于内存,且带宽利用率非常高。

使用 Bandwidth Utilization Histogram,并在 Bandwidth Domain 下拉菜单中选择 QPI 后,可以看出 QPI 带宽已降低至中低水平。

图 6:采用首次接触策略的 QPI 带宽直方图

切换至自下而上视图并查看时间线,我们可以看出,DRAM 带宽利用率已达到平衡状态或平分于两个插槽,且 QPI 流量降低了 3 倍。

图 7:QPI 流量降低且各插槽 DRAM 带宽达到平衡

 

6. 整体性能对比

我们运行修改版应用,如下所示:

./run_on_xeon.pl bin/iso3dfd_dev06_cpu_avx2.exe 448 2016 1056 \ 
         10 448 24 96 compact 44                             

n1=448 n2=2016 n3=1056 nreps=10 num_threads=44 HALF_LENGTH=8
n1_thrd_block=448 n2_thrd_block=24 n3_thrd_block=96
allocating prev, next and vel: total 11694.4 Mbytes
-------------------------------
time:           1.70 sec
throughput:  5682.07 MPoints/s
flops:        346.61 GFlops

凭借更出色的内存访问特性,应用吞吐率现已从 2765 MPoints/秒提高到 5682 MPoints/秒,加快了 2 倍。

为了确认这种性能改进是否具有一致性,两种版本的代码运行了 10 次,且每次运行包含 100 次迭代。 为清晰起见,原始 dev06 版代码使用静态和动态 OpenMP 调度运行,以区分换成 OpenMP 调度与引进首次接触策略后分别实现的性能提升。我们可以看出,采用静态 OpenMP 调度运行修改后,NUMA 感知型代码可以实现较高的性能提升。 采用动态调度时,OpenMP 线程和高速缓存数据块(本应用中 OpenMP 线程的工作单位)之间的映射在每次迭代中具有不确定性。 因此,采用动态 OpenMP 调度时,首次接触策略的影响并不显著。

                

图 8:性能差异

 

7. 系统配置

本文图表中提供的性能测试结果基于以下测试系统。 如欲了解更多信息,请访问:http://www.intel.com/performance

组件规格
系统双路服务器
主机处理器英特尔® 至强™ 处理器 E5-2699 V4 @ 2.20 GHz
内核/线程44/44
主机内存64 GB/插槽
编译器英特尔® C++ 编译器版本 16.0.2
分析器英特尔® VTune™ Amplifier XE 2016 Update 2
主机操作系统Linux,版本 3.10.0-327.el7.x86_64

 

8. 参考资料

采用各项同性 (ISO) 的三维有限差分 (3DFD) 代码的八项优化措施 (https://software.intel.com/zh-cn/articles/eight-optimizations-for-3-dimensional-finite-difference-3dfd-code-with-an-isotropic-iso)

英特尔® VTune™ Amplifier XE 2016 (https://software.intel.com/zh-cn/intel-vtune-amplifier-xe)

英特尔® VTune™ Amplifier XE - 解析内存使用数据 (https://software.intel.com/zh-cn/node/544170)

非一致性内存访问 (https://en.wikipedia.org/wiki/Non-uniform_memory_access)

numactl - Linux man 页面 (http://linux.die.net/man/8/numactl)

 

关于作者

Sunny Gogar

Sunny Gogar
软件工程师

Sunny Gogar 获得了佛罗里达大学电子与计算机工程专业的硕士学位,以及印度孟买大学电子与电信学专业的学士学位。目前在英特尔公司软件及服务事业部担任软件工程师。他的兴趣在于面向多核和众核处理器架构的并行编程与优化。

 


[1] 本文对比的所有版本都使用了 -fma 等面向最新英特尔处理器的编译时间标记。

[2]numactl - 控制用于进程或共享内存的 NUMA 策略

 

声明

英特尔技术的特性和优势取决于系统配置,并需要兼容的硬件、软件或需要激活服务。 实际性能会因您使用的具体系统配置的不同而有所差异。 请联系您的系统制造商或零售商,或访问 intel.com,了解更多信息。

本文档不代表英特尔公司或其它机构向任何人明确或隐含地授予任何知识产权。

英特尔明确拒绝所有明确或隐含的担保,包括但不限于对于适销性、特定用途适用性和不侵犯任何权利的隐含担保,以及任何对于履约习惯、交易习惯或贸易惯例的担保。

本文包含尚处于开发阶段的产品、服务和/或流程的信息。 此处提供的所有信息可随时更改,恕不另行通知。 联系您的英特尔代表,了解最新的预测、时间表、规格和路线图。

本文所述的产品和服务可能包含与宣称的规格不符的缺陷或失误。 英特尔提供最新的勘误表备索。

如欲获取本文提及的带订购编号的文档副本,可致电 1-800-548-4725,或访问 www.intel.com/design/literature.htm

英特尔、Intel 标识、Xeon Phi、至强融核、VTune、Xeon 和至强是英特尔在美国和/或其他国家(地区)的商标。

*其他的名称和品牌可能是其他所有者的资产。

英特尔公司 © 2016 年版权所有。


该示例源代码根据英特尔示例源代码许可协议发布。

 

Para obtener información más completa sobre las optimizaciones del compilador, consulte nuestro Aviso de optimización.