减少 OpenMP 并行中的迭代次数

Decreasing number of iterations in OpenMP parallel for

本文关键字:迭代 OpenMP 并行 减少      更新时间:2023-10-16

我在C++程序中有一个parallel for,它必须循环到一定次数的迭代。每次迭代都会计算算法的可能解决方案,一旦找到有效的解决方案,我想退出循环(如果完成一些额外的迭代也可以)。我知道迭代次数应该从一开始就固定在parallel for中,但由于我没有在以下代码中增加迭代次数,因此是否可以保证线程在继续当前迭代之前检查条件?

void fun()
{
  int max_its = 100;
  #pragma omp parallel for schedule(dynamic, 1)
  for(int t = 0; t < max_its; ++t)
  {
    ...
    if(some condition)
      max_its = t; // valid to make threads exit the for?
  }
}

修改循环计数器适用于 OpenMP 工作共享结构的大多数实现,但该程序将不再符合 OpenMP,并且无法保证该程序可与其他编译器配合使用。

由于 OP 可以通过一些额外的迭代进行,因此取消 OpenMP 将是要走的路。 OpenMP 4.0 正是为此目的引入了"取消"结构。 它将请求终止工作共享构造并将线程传送到其末尾。

void fun()
{
  int max_its = 100;
#pragma omp parallel for schedule(dynamic, 1)
  for(int t = 0; t < max_its; ++t)
  {
    ...
    if(some condition) {
#pragma omp cancel for
    }
#pragma omp cancellation point for
  }
}
请注意,在性能方面可能会付出代价,

但如果中止循环时整体性能更好,则可能需要接受这一点。

在 OpenMP

4.0 之前的实现中,唯一符合 OpenMP 的解决方案是使用 if 语句尽快接近循环的常规结束,而无需执行实际的循环主体:

void fun()
{
  int max_its = 100;
#pragma omp parallel for schedule(dynamic, 1)
  for(int t = 0; t < max_its; ++t)
  {
    if(!some condition) {
      ... loop body ...
    }
  }
}

希望对您有所帮助!

干杯 -迈克尔

你不能

修改max_its,因为标准说它必须是循环不变表达式

但是,您可以做的是使用布尔共享变量作为标志:

void fun()
{
  int max_its = 100;
  bool found = false;
  #pragma omp parallel for schedule(dynamic, 1) shared(found)
  for(int t = 0; t < max_its; ++t)
  {
    if( ! found ) {
    ...
    }
    if(some condition) {
  #pragma omp atomic
      found = true; // valid to make threads exit the for?
    }
  }
}

这种逻辑也可以通过任务而不是工作共享构造来实现。代码的草图如下所示:

void algorithm(int t, bool& found) {
#pragma omp task shared(found)
{
  if( !found ) {
    // Do work
    if ( /* conditionc*/ ) {
      #pragma omp atomic
      found = true
    }
  }
} // task
} // function

void fun()
{
  int max_its = 100;
  bool found  = false;
  #pragma omp parallel 
  {
    #pragma omp single
    {
      for(int t = 0; t < max_its; ++t)
      {
        algorithm(t,found);
      }
    } // single
  } // parallel
}

这个想法是单个线程创建max_its任务。每个任务都将分配给一个等待线程。如果某些任务找到有效的解决方案,则所有其他任务都将通过找到的共享变量通知。

如果some_condition是一个"始终有效"的逻辑表达式,那么你可以这样做:

for(int t = 0; t < max_its && !some_condition; ++t)

这样,很明显需要!some_condition才能继续循环,并且无需阅读其余代码即可发现"如果some_condition,循环结束"

否则(例如,如果some_condition是循环内部某些计算的结果,并且将some_condition"移动"到 for 循环条件很复杂,那么使用 break 显然是正确的做法。