分叉一次,然后在不同的程序中使用线程?

时间:2014-01-20 15:17:52

标签: c++ fortran openmp

如果我在我的主程序中分叉然后在单个指令中调用子程序,那么如果我在这个子程序中输入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 ++中的解决方案是什么?

3 个答案:

答案 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()调用后正常工作,但它不会使用与父进程相同的线程。