初始化变量以进行 omp 缩减

Initialize variable for omp reduction

本文关键字:omp 缩减 变量 初始化      更新时间:2023-10-16

OpenMP 标准指定了归约变量的初始值。那么我是否必须初始化变量,以及在以下情况下我将如何执行此操作:

int sum;
//...
for(int it=0;i<maxIt;i++){
#pragma omp parallel
{
  #pragma omp for nowait
  for(int i=0;i<ct;i++)
    arrayX[i]=arrayY[i];
  sum = 0;
  #pragma omp for reduction(+:sum)
  for(int i=0;i<ct;i++)
    sum+=arrayZ[i];
}
//Use sum
}

请注意,我只使用 1 个并行区域来最小化开销并允许在第一个循环中使用 nowait。按原样使用将导致数据竞争 (IMO),因为来自其他线程启动后第一个循环的线程将重置总和。
当然,我可以在外部循环的顶部执行此操作,但在一般情况下,对于大型代码库,您可能会忘记您需要或已将其设置在那里,从而产生意想不到的结果。
"omp single"在这里有帮助吗?我怀疑当线程 A 执行单个线程时,另一个线程可能已经进入缩减循环。"omp 屏障"是可能的,但我想避免这种情况,因为它击败了"nowait"。

最后再举一个例子:

#pragma omp parallel
{
  sum = 0;
  #pragma omp for reduction(+:sum)
  for(int i=0;i<ct;i++)
    sum+=arrayZ[i];
  //Use sum
  sum = 0;
  #pragma omp for reduction(+:sum)
  for(int i=0;i<ct;i++)
    sum+=arrayZ[i];
  //Use sum
}

我将如何在此处(重新)初始化?

编辑:这个答案是错误的,因为它做出了一个不在OpenMP规范中的假设。由于接受的答案无法删除,因此我将其留在这里作为一个例子,人们应该始终怀疑和验证在互联网上找到的代码和/或语句。

实际上,代码不会表现出数据竞赛:

#pragma omp parallel
{
   ...
   sum = 0;
   #pragma omp for reduction(+:sum)
   for(int i=0;i<ct;i++)
     sum+=arrayZ[i];
   ...
}

这里发生的情况是在工作共享构造中创建sum的私有副本,并将其初始化为 0+运算符的初始化值)。每个本地副本都由循环体更新。给定线程完成后,它将在for构造末尾存在的隐式屏障处等待。一旦所有线程都到达屏障,它们的本地sum副本就会相加,结果将添加到共享值中。

所有线程可能在不同的时间执行sum = 0;并不重要,因为它的值仅在达到屏障后更新。想想上面的代码,执行如下:

...
sum = 0;
// Start of the for worksharing construct
int local_sum = 0;                     // ^
for(int i = i_start; i < i_end; i++)   // | sum not used here
  local_sum += arrayZ[i];              // v
// Implicit construct barrier
#pragma omp barrier
// Reduction
#pragma omp atomic update
sum += local_sum;
#pragma omp barrier
// End of the worksharing construct
...

这同样适用于第二个示例。

OpenMP 规范没有规定何时以及如何更新原始值,并强制要求使用同步(OpenMP,第 205 页):

若要避免争用条件,原始列表项的并发读取或更新必须与由于reduction计算而发生的原始列表项的更新同步。

在这两个示例中,都需要在分配给sum后进行barrier,或者需要一个single构造(不带nowait)以防止竞争条件。