NUMA 处理器上的 OpenMP 内存分配

Posted

技术标签:

【中文标题】NUMA 处理器上的 OpenMP 内存分配【英文标题】:OpenMP Memory Allocation on NUMA Processor 【发布时间】:2017-07-11 22:31:21 【问题描述】:

我目前正在尝试在 Maestro 处理器上使用 OpenMP 加速简单的矩阵减法基准测试,该处理器具有 NUMA 架构并基于 Tilera Tile64 处理器。 Maestro 板有 49 个处理器,以 7x7 配置的二维阵列排列。每个内核都有自己的 L1 和 L2 缓存。板的布局可以在这里看到:http://i.imgur.com/naCWTuK.png

我不熟悉编写“NUMA 感知”应用程序的想法,但我所读到的主要共识是,数据局部性是最大化性能的重要组成部分。在内核之间并行化代码时,我应该尽可能将正在使用的数据保持在线程本地进行处理。

对于这个矩阵减法基准 (C[i] = A[i] - B[i]),我认为最好为每个线程分配其自己的私有 A、B 和 C 数组,其大小为是总工作量除以线程数。因此,例如,如果数组的总大小为 6000*6000,并且我试图在 20 个线程中并行化它,我将分配大小为 (6000*6000)/20 的私有数组。每个线程都会在自己的私有数组上进行减法运算,然后我会将结果收集回总大小为 6000*6000 的最终数组中。例如(没有将每个线程的结果收集到最终数组中):

int threads = 20;
int size = 6000;
uint8_t *C_final = malloc(sizeof(uint8_t)*(size*size));
#pragma omp parallel num_threads(threads) private(j)

     uint8_t *A_priv = malloc(sizeof(uint8_t)*((size*size)/threads));
     uint8_t *B_priv = malloc(sizeof(uint8_t)*((size*size)/threads));
     uint8_t *C_priv = malloc(sizeof(uint8_t)*((size*size)/threads));

     for(j=0; j<((size*size)/threads); j++)
       
            A_priv[j]=100;
            B_priv[j]=omp_get_thread_num();
            C_priv[j]=0;
       

     for(j=0; j<((size*size)/threads); j++)
       
           C_priv[j] = A_priv[j]-B_priv[j];
       

数组的初始值是任意的,我只有 omp_get_thread_num() 在那里,所以我从每个线程的 C_priv 中得到不同的值。我目前正在试验开发板上的用户动态网络,该网络提供硬件以在 CPU 之间路由数据包,以便将所有单独的线程结果累积到最终结果数组中。

我已经通过这种方式实现了加速,同时使用 OMP_PROC_BIND=true 固定线程,但我担心将单个结果累积到最终数组中可能会导致开销抵消加速。

这是解决此类问题的正确方法吗?对于像这样使用 OpenMP 的问题,我应该研究什么类型的技术来加快 NUMA 架构的速度?

编辑:

为了澄清,这是我最初尝试的,我注意到执行时间比我只是串行运行代码要慢:

     int threads = 20;
     int size = 6000;
     uint8_t *A_priv = malloc(sizeof(uint8_t)*(size*size));
     uint8_t *B_priv = malloc(sizeof(uint8_t)*(size*size));
     uint8_t *C_priv = malloc(sizeof(uint8_t)*(size*size));

     int i;
     for(i=0; i<(size*size); i++)
     
       A[i] = 10;
       B[i] = 5;
       C[i] = 0;
     

     #pragma omp parallel for num_threads(threads)
     for(i=0; i<(size*size); i++)
     
       C[i] = A[i] - B[i];
     

在看到我在使用 OpenMP 时执行时间变慢后,我尝试调查为什么会出现这种情况。似乎数据局部性是问题所在。这个假设是基于我阅读的关于 NUMA 架构的内容。

我很难弄清楚如何缓解拖慢它的瓶颈。对于类似的问题,我找到了一些帮助:OpenMP: for schedule,它负责将数据分配给每个线程,以便每个线程处理其本地数据。

我只是觉得像矩阵减法这样简单的事情在使用 OpenMP 时应该不难提高性能。我不确定如何弄清楚瓶颈到底是什么以及如何缓解它。

【问题讨论】:

您是否考虑过使用消息传递 (MPI) 代替?使用 MPI,您可以更明确地控制内存布局和进程之间的通信。 我认为您混淆了 NUMA、缓存和数据局部性。您的问题的详细答案将非常广泛并且需要对系统上的 NUMA 内存分配策略有广泛的了解并且需要有关应用程序中内存访问模式的更多详细信息。一个普遍的答案是让你的代码保持高水平,直到测量显示出重大的性能问题。不根据具体的测量结果提出一般性建议是徒劳的。我也不确定如果数据无论如何都驻留在共享内存中,为什么您甚至需要/想要累积结果。 我在原始问题中添加了一个编辑,以显示我最初尝试的内容,这只是一个简单的 OpenMP for 循环,与连续运行减法相比,我发现性能有所下降。 是性能低还是这只是过早的优化? 如果我执行一个简单的 OpenMP for 循环(在原始问题的示例中进行了编辑),我发现性能比仅连续运行它时更差。这不仅仅是我正在做的矩阵减法的情况,我已经看到了同样的情况,例如矩阵乘法,但我试图从尽可能简单的事情开始。当我将分配分解为每个线程的私有数组时,我看到性能有所提高,但现在每个线程都有自己的结果数组,而不是一个累积的结果。 【参考方案1】:

在快速搜索和扫描 TILE64 数据表时,该架构看起来不像您通过 oprofile、VTune 或 xperf 等工具在 x86 上使用的那样公开性能计数器。如果没有这些,您将不得不自己设计一些实验来迭代地缩小代码的哪些部分是热门的以及为什么 - 在没有微架构文档以及工具来指示您的代码如何使用硬件的情况下,有点逆向工程任务。

关于从哪里开始的一些想法:

    做一些缩放实验。曲线中是否有一个拐点,超过一定的问题大小或线程数对整体性能有很大影响?这个数字是否暗示了与内存层次结构中某个级别的大小、处理器网格的维度或类似的某些明确的关系? 通过程序记录几个点的执行时间。例如,大概了解一下 malloc 与第一个循环与第二个循环花费了多少时间可能会很有用。 “我已经通过这种方式实现了加速,同时使用 OMP_PROC_BIND=true 固定线程,但我担心将单个结果累积到最终数组中可能会导致开销抵消加速。” - 这种担忧也可以通过经验测试,特别是如果您正在处理足够大的问题规模,那么(2)中的计时器精度对于隔离收集步骤所花费的时间与完全可并行化的部分所用的时间不是问题。 尝试不同的操作 - 例如,加法或元素除法而不是减法,看看是否会改变结果。在许多架构上,不同的算术运算具有不同的延迟和吞吐量。如果您查找并发现 TILE64 就是这种情况,那么进行这样的更改并检测第二个示例的运行时可能会告诉您一些有用的信息,即串行运行它所花费的时间实际上与数据有关局部性问题与启动时间或与 OpenMP 运行时相关的其他开销可能与整体结果有关,因为它与小问题规模的关系比并行实现的正确并行部分实际运行速度更慢。 您可以检查生成的程序集。假设编译器会在您发布的示例中执行基本相同的操作似乎是合理的,但在查看奇怪的性能时并不一定像您希望的那样强烈。也许有一些关于代码大小或布局的变化,有/没有 OpenMP,或者从一种并行方法转移到另一种并行方法时,比如指令缓存的使用、保留站的可用性或 ROB 条目(如果 TILE64 有这些东西)......?谁知道呢,直到你看。

【讨论】:

以上是关于NUMA 处理器上的 OpenMP 内存分配的主要内容,如果未能解决你的问题,请参考以下文章

Intel Xeon 上的 NUMA:内存区域是不是必须具有相同的大小?

如何将所有内存分配限制到一个 NUMA 节点

numa_alloc_onnode() 分配的内存更多吗?

有没有办法为 NUMA 中的数据分配特定的内存?

ceph 14 后 ceph adm支持自动配置numa

NUMA 会影响内存带宽,还是只是延迟?