如果我在我的主程序中分叉然后在单个指令中调用子程序,那么如果我在这个子程序中输入OMP并行指令会有什么行为?
我的猜测/希望是使用现有的线程,因为它们现在都应该没有任何关系。
伪示例:
double A[];
int main() {
#pragma omp parallel num_threads(2)
{
#pragma omp single
{
for (int t=0; t<1000; t++) {
evolve();
}
}
}
}
void evolve() {
#pragma omp parallel for num_threads(2)
for (int i=0; i<100; i++) {
do_stuff(i);
}
}
void do_stuff(int i) {
// expensive calculation on array element A[i]
}
因为evolve()
经常被调用,所以在这里分叉会导致很多开销,所以我只想做一次,然后从单个线程调用evolve()
并拆分工作通过现有线程调用do_stuff()
。
对于Fortran来说,这似乎有效。在一个使用2个线程的简单示例中,我的速度提高了大约80-90%。但对于C ++,我得到了不同的行为,只有执行单指令的线程用于evolve()
我使用主程序中的任务指令修复了问题并将限制传递给evolve()
,但这看起来像是一个笨拙的解决方案......
为什么Fortran和C ++中的行为不同,C ++中的解决方案是什么?
答案 0 :(得分:1)
我相信孤立指令是您案件中最干净的解决方案:
double A[];
int main() {
#pragma omp parallel num_threads(2)
{
// Each thread calls evolve() a thousand times
for (int t=0; t<1000; t++) {
evolve();
}
}
}
void evolve() {
// The orphaned construct inside evolve()
// will bind to the innermost parallel region
#pragma omp for
for (int i=0; i<100; i++) {
do_stuff(i);
} // Implicit thread synchronization
}
void do_stuff(int i) {
// expensive calculation on array element A[i]
}
这样可行(因为标准的第2.6.1节):
循环区域绑定到最里面的并行区域
也就是说,在您的代码中,您使用的是嵌套并行构造。为确保启用它们,您必须将环境变量OMP_NESTED
设置为true
,否则(引用最新标准的附录E):
OMP_NESTED环境变量:如果该值既不是 true 也不是 false ,则行为是实现定义的
答案 1 :(得分:1)
不幸的是,在所有情况下,您的代码可能无法按预期运行。如果你有这样的代码结构:
void foo() {
#pragma omp parallel
#pragma omp single
bar();
}
void bar() {
#pragma omp parallel
printf("...)";
}
当进入bar中的并行区域时,请求OpenMP创建一个新的线程组。 OpenMP调用“嵌套并行”。但是,究竟发生了什么取决于您使用的OpenMP的实际实现以及OMP_NESTED的设置。
OpenMP实现不需要支持嵌套并行性。如果一个实现忽略了bar中的并行区域并且只用一个线程执行它,那将是完全合法的。如果实现支持,OMP_NESTED可用于打开和关闭嵌套。
在你的情况下,偶然的事情顺利进行,因为除了一个之外你发送了所有线程。然后,该线程创建了一个全尺寸线程的新团队(可能是新线程,而不是重用旧线程)。如果省略了单个构造,则很容易获得数千个线程。
不幸的是,OpenMP不支持您的模式来创建并行团队,让一个线程执行调用堆栈,然后通过工作共享构造在其他团队成员之间分配工作。如果您需要此代码模式,唯一的解决方案将是OpenMP任务。
干杯, -michael
答案 2 :(得分:0)
你的例子实际上并没有调用fork()
,所以我怀疑你并不是指系统调用意义上的分叉(即重复你的过程)。但是,如果这真的是你的意思,我怀疑大多数OpenMP实现在分叉进程中无法正常工作。通常,fork()
次调用不会保留线程。如果您使用的OpenMP实现注册pthread_atfork()
处理程序,它可以在fork()
调用后正常工作,但它不会使用与父进程相同的线程。