为啥openmp不根据手动NUMA绑定放置线程?

Posted

技术标签:

【中文标题】为啥openmp不根据手动NUMA绑定放置线程?【英文标题】:Why doesn't openmp place threads based on manual NUMA bind?为什么openmp不根据手动NUMA绑定放置线程? 【发布时间】:2018-03-07 14:22:37 【问题描述】:

我正在构建一个可识别 numa 的处理器,该处理器绑定到给定的套接字并接受 lambda。这是我所做的:

#include <numa.h>
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <thread>
#include <vector>

using namespace std;

unsigned nodes = numa_num_configured_nodes();
unsigned cores = numa_num_configured_cpus();
unsigned cores_per_node = cores / nodes;

int main(int argc, char* argv[]) 
    putenv("OMP_PLACES=sockets(1)");
    cout << numa_available() << endl;  // returns 0
    numa_set_interleave_mask(numa_all_nodes_ptr);
    int size = 200000000;
    for (auto i = 0; i < nodes; ++i) 
        auto t = thread([&]() 
            // binding to given socket
            numa_bind(numa_parse_nodestring(to_string(i).c_str()));
            vector<int> v(size, 0);
            cout << "node #" << i << ": on CPU " << sched_getcpu() << endl;
#pragma omp parallel for num_threads(cores_per_node) proc_bind(master)
            for (auto i = 0; i < 200000000; ++i) 
                for (auto j = 0; j < 10; ++j) 
                    v[i]++;
                    v[i] *= v[i];
                    v[i] *= v[i];
                
            
        );
        t.join();
    

但是,所有线程都在套接字 0 上运行。似乎numa_bind 没有将当前线程绑定到给定的套接字。第二个numa处理器——Numac 1输出node #1: on CPU 0,它应该在CPU 1上。那么出了什么问题?

【问题讨论】:

minimal reproducible example 需要。它需要是完整的最小的 嗯,我将尝试缩小示例。不过已经完成了。 @linspectable 现在要小得多。 通常,在代码中将多个线程范例(例如 C++11 线程和 OpenMP)组合在一起并不是一个好主意。两者都在内部使用 Pthreads 库,这可能会引起麻烦。 另外,请注意:“在可以使用此库中的任何其他调用之前,必须调用numa_available()。如果它返回-1,则此库中的所有其他函数都是未定义的。” 【参考方案1】:

这完全符合我的预期:

#include <cassert>
#include <iostream>
#include <numa.h>
#include <omp.h>
#include <sched.h>

int main() 
   assert (numa_available() != -1);

   auto nodes = numa_num_configured_nodes();
   auto cores = numa_num_configured_cpus();
   auto cores_per_node = cores / nodes;

   omp_set_nested(1);

   #pragma omp parallel num_threads(nodes)
   
      auto outer_thread_id = omp_get_thread_num();
      numa_run_on_node(outer_thread_id);

      #pragma omp parallel num_threads(cores_per_node)
      
         auto inner_thread_id = omp_get_thread_num();

         #pragma omp critical
         std::cout
            << "Thread " << outer_thread_id << ":" << inner_thread_id
            << " core: " << sched_getcpu() << std::endl;

         assert(outer_thread_id == numa_node_of_cpu(sched_getcpu()));
      
   

程序首先在我的双路服务器上创建 2 个(外部)线程。然后,它将它们绑定到不同的套接字(NUMA 节点)。最后,它将每个线程分成 20 个(内部)线程,因为每个 CPU 有 10 个物理内核并启用了超线程。

所有内部线程都在与其父线程相同的套接字上运行。那是外线程 0 的内核 0-9 和 20-29,外线程 1 的内核 10-19 和 30-39。(在我的情况下,sched_getcpu() 返回了 0-39 范围内的虚拟内核数。 )

请注意,没有 C++11 线程,只有纯 OpenMP。

【讨论】:

谢谢。这是否意味着最初的问题是 c++ 线程和 openmp 线程的冲突? @Amos 我会非常小心地做出这样的结论。你的代码可能还有很多其他问题,在我看来有点乱。 好吧,用 std::thread 替换外循环会重现问题。 @Amos 不错的发现。这就是为什么永远不要将不同的线程库混合在一起的原因。

以上是关于为啥openmp不根据手动NUMA绑定放置线程?的主要内容,如果未能解决你的问题,请参考以下文章

NUMA 处理器上的 OpenMP 内存分配

为啥 OpenMP 不并行化 vtk IntersectWithLine 代码

为啥 OpenMP 程序只在一个线程中运行

如何从程序中设置 OpenMP 线程的数量?

如何不等待 OpenMP 中的其他线程?

OpenMp根据变量设置并行循环的线程数