C++ NUMA 优化

C++ NUMA Optimization

我正在处理最初为多核处理器系统开发的遗留应用程序。为了利用多核处理,已经使用了 OpenMP 和 PPL。 现在一项新要求是 运行 具有多个 NUMA 节点的系统上的软件。目标 OS 是 Windows 7 x64.

我进行了多次测量,发现将应用程序分配给单个 NUMA 节点时执行时间最佳,因此浪费了一个完整的处理器。应用程序的许多部分执行数据并行算法,例如并行处理向量的每个元素,并将结果写入另一个向量,如下例

std::vector<int> data;
std::vector<int> res;

// init data and res

#pragma omp parallel for
for (int i = 0; i < (int) data.size(); ++i)
{  
  res[i] = doExtremeComplexStuff(data[i]);
}

据我所知,此类算法的性能下降是由第二个 NUMA 节点的非本地内存访问引起的。那么问题来了,如何让应用性能更好。

是否以某种方式透明地加速了对非本地内存的只读访问(例如,通过 OS 将数据从一个节点的本地内存复制到另一个节点的本地内存)? 我是否必须拆分问题大小并将输入数据复制到相应的 NUMA 节点,对其进行处理,然后再次组合所有 NUMA 节点的数据以提高性能?

如果是这种情况,是否有标准容器的替代品,因为它们在分配内存时不支持 NUMA?

当您分配动态内存时(例如 std::vector 所做的),您可以有效地从虚拟内存 space 中获取一定范围的页面。当程序首次访问特定页面时,会触发页面错误并请求物理内存中的某些页面。通常,此页面位于产生页面错误的核心的本地物理内存中,这称为 first touch 策略。

在您的代码中,如果您的 std::vector 缓冲区的页面首先被单个(例如,主)线程访问,那么这些向量的所有元素可能最终都在本地内存中单个 NUMA 节点。然后,如果您将程序拆分为在所有 NUMA 节点上运行的线程,则某些线程在使用这些向量时会访问远程内存。

因此,解决方案是分配 "raw memory" 然后 "touch" 它首先与所有线程使用相同的方式,然后这些线程在处理阶段访问它。不幸的是,这不容易用 std::vector 实现,至少对于标准分配器来说是这样。能换成普通的动态数组吗?我会先尝试一下,看看它们对首次接触策略的初始化是否有帮助:

int* data = new int[N];
int* res = new int[N];

// initialization with respect to first touch policy
#pragma omp parallel for schedule(static)
for (int i = 0; i < N; i++) {
   data[i] = ...;
   res[i] = ...;
}

#pragma omp parallel for schedule(static)
for (int i = 0; i < N; i++)
   res[i] = doExtremeComplexStuff(data[i]);

使用 static 调度,元素到线程的映射在两个循环中应该完全相同。


但是,我不相信您的问题是访问这两个向量时由NUMA 效应引起的。正如您调用函数 doExtremeComplexStuff 一样,该函数在运行时似乎非常昂贵。如果这是真的,那么与函数调用相比,即使访问远程 NUMA 内存也可能快得可以忽略不计。整个问题可以隐藏在这个函数中,但我们不知道它做了什么。