如果由另一个 OpenMP 程序调用,则外部调用的 OpenMP 程序仅使用一个线程运行
Externally called OpenMP program runs with only one thread if called by another OpenMP program
我有两个程序A和B,都是用g++-5.1和openmp编译的。
代码是 运行 inside Scientific Linux 7.1.
g++ <program>.cpp -fopenmp -o <program>
程序 (A) 通过 std::system 调用启动程序 (B)。
程序的调用图如下所示:
|- A
|- B
程序 B 应该使用系统上所有可用的 CPU 内核,但它只使用一个线程。
如果程序 A 是在没有 -fopenmp
标志的情况下编译的,那么程序 B 将使用所有可用的内核。
即使程序 B 不再是 A 的子进程 (std::system("setsid ./B &")
),它也只使用一个线程。调用图:
|- B
...
|- A
为什么会发生这种行为?如何让程序 B 在被程序 A 调用时利用所有内核?
再次奇怪的是,如果调用者没有使用 -fopenmp 编译,我会得到预期的行为。我尝试过的其他事情:使用 execve posix_spawn 生成子进程,中间有一个 bash 实例。使用编译器 g++-4.8.3 也会出现此问题。我没主意了。
程序A:A.cpp
#include <cstdlib>
int main(int argc, const char** argv) {
std::system("setsid ./B 100000000000 &");
}
程序 B:B.cpp
#include "omp.h"
#include <cstdlib>
#include <iostream>
// ignore the workload, I just need something to spin the CPU
double workload(size_t num_steps)
{
size_t i;
double x=0;
double sum = 0.0;
double step = 1.0 / (double) num_steps;
#pragma omp parallel private(i,x)
{
#pragma omp for reduction(+:sum) schedule(dynamic, 100)
for (i=0; i<num_steps; i=i+1){
x=(i+0.5)*step;
sum = sum + 4.0/(1.0+x*x);
}
}
return step*sum;
}
int main(int argc, const char** argv) {
size_t a = strtoull(argv[1], NULL, 10);
std::cout << a << " " << workload(a) << "\n";
return 0;
}
大多数 OpenMP 运行时库的初始化发生在它们的构造函数中,这些构造函数在进程生命周期的早期执行。因此,不可能阻止 OMP_PROC_BIND
对程序 A 的绑定效果,但可以在生成程序 B 之前抵消它。有多种方法,但我立即想到了两种:
1) 在对 std::system()
的调用中使用 taskset
覆盖子进程的 CPU affinity mask:
std::system("taskset -c 0-63 ./B 100000000000");
-c 0-63
参数生成一个 CPU 亲和掩码,设置了 64 位,这对大多数当前一代的多核系统来说应该是好的(除非该程序在 Intel Xeon Phi 或某些奇特的硬件上运行就像我们的 Bull Coherent Switch 耦合胖节点)。显然,如果 taskset
没有安装在系统上(作为 util-linux
的一部分,它应该在许多系统上默认安装)。
2) 在调用std::system()
之前使用sched_setaffinity(2)
或pthread_setaffinity_np(3)
重置A 的CPU affinity mask。寻找 here 的灵感。
3) 如果你能负担得起外部依赖,hwloc library 有一个非常好的 API 可以用来获取和操纵 CPU 亲和力。它也是 cross-platform,也适用于 Windows。
选项 3 是最干净的选项,因为您不必预先在 taskset
参数或传递给调度程序函数的 CPU 参数集中硬编码足够宽的掩码。
我有两个程序A和B,都是用g++-5.1和openmp编译的。 代码是 运行 inside Scientific Linux 7.1.
g++ <program>.cpp -fopenmp -o <program>
程序 (A) 通过 std::system 调用启动程序 (B)。 程序的调用图如下所示:
|- A
|- B
程序 B 应该使用系统上所有可用的 CPU 内核,但它只使用一个线程。
如果程序 A 是在没有 -fopenmp
标志的情况下编译的,那么程序 B 将使用所有可用的内核。
即使程序 B 不再是 A 的子进程 (std::system("setsid ./B &")
),它也只使用一个线程。调用图:
|- B
...
|- A
为什么会发生这种行为?如何让程序 B 在被程序 A 调用时利用所有内核?
再次奇怪的是,如果调用者没有使用 -fopenmp 编译,我会得到预期的行为。我尝试过的其他事情:使用 execve posix_spawn 生成子进程,中间有一个 bash 实例。使用编译器 g++-4.8.3 也会出现此问题。我没主意了。
程序A:A.cpp
#include <cstdlib>
int main(int argc, const char** argv) {
std::system("setsid ./B 100000000000 &");
}
程序 B:B.cpp
#include "omp.h"
#include <cstdlib>
#include <iostream>
// ignore the workload, I just need something to spin the CPU
double workload(size_t num_steps)
{
size_t i;
double x=0;
double sum = 0.0;
double step = 1.0 / (double) num_steps;
#pragma omp parallel private(i,x)
{
#pragma omp for reduction(+:sum) schedule(dynamic, 100)
for (i=0; i<num_steps; i=i+1){
x=(i+0.5)*step;
sum = sum + 4.0/(1.0+x*x);
}
}
return step*sum;
}
int main(int argc, const char** argv) {
size_t a = strtoull(argv[1], NULL, 10);
std::cout << a << " " << workload(a) << "\n";
return 0;
}
大多数 OpenMP 运行时库的初始化发生在它们的构造函数中,这些构造函数在进程生命周期的早期执行。因此,不可能阻止 OMP_PROC_BIND
对程序 A 的绑定效果,但可以在生成程序 B 之前抵消它。有多种方法,但我立即想到了两种:
1) 在对 std::system()
的调用中使用 taskset
覆盖子进程的 CPU affinity mask:
std::system("taskset -c 0-63 ./B 100000000000");
-c 0-63
参数生成一个 CPU 亲和掩码,设置了 64 位,这对大多数当前一代的多核系统来说应该是好的(除非该程序在 Intel Xeon Phi 或某些奇特的硬件上运行就像我们的 Bull Coherent Switch 耦合胖节点)。显然,如果 taskset
没有安装在系统上(作为 util-linux
的一部分,它应该在许多系统上默认安装)。
2) 在调用std::system()
之前使用sched_setaffinity(2)
或pthread_setaffinity_np(3)
重置A 的CPU affinity mask。寻找 here 的灵感。
3) 如果你能负担得起外部依赖,hwloc library 有一个非常好的 API 可以用来获取和操纵 CPU 亲和力。它也是 cross-platform,也适用于 Windows。
选项 3 是最干净的选项,因为您不必预先在 taskset
参数或传递给调度程序函数的 CPU 参数集中硬编码足够宽的掩码。