3

我有一个用于物理模拟的简单程序。我想知道如何在 OpenMP 中实现某种线程范式。

int main()
{
#define steps (100000)
   for (int t = 0;t < steps; t++)
   {
     firstParallelLoop();
     secondParallelLoop();
     if (!(t%100))
     {
        checkpoint();
     }
   }
}
void firstParallelLoop()
{// In another file.c
  #pragma omp parallel for
   for (int i = 0; i < sizeOfSim;i++)
   {
     //Some atomic floating point ops.
   }
}

以前,我使用 pthreads 并在我的双核笔记本电脑上获得了 1.7 的加速。使用 OpenMP 时,我似乎无法获得任何加速。我怀疑问题在于线程组/池正在迅速创建和销毁,并产生灾难性的影响。

在我的 pthreads 实现中,我需要确保没有创建新线程,并且我的程序表现为客户端-服务器。在 pthreads 方案中,main() 是一个服务器,对 firstParallelLoop 的调用将释放触发线程重新处理数据的互斥体/信号量。

当我查看 CPU 利用率时,我预计它会超过 30%(4 核,2 是 HT),但它保持在 27 左右......

如何让 OpenMP 做类似的事情?如何告诉 OpenMP 重用我的线程?

4

1 回答 1

6

GCC OpenMP 运行时libgomp通过类似于线程池的方式在 POSIX 系统上实现线程组——线程仅在遇到第一个并行区域时创建,每个线程运行一个无限工作循环。进入和退出平行区域是通过障碍实现的。默认情况下libgomp使用忙等待和睡眠的组合来实现屏障。忙碌等待的数量由OMP_WAIT_POLICY环境变量控制。如果未指定,则在屏障上等待的线程将忙于等待 300000 次旋转(100000 次旋转/毫秒时为 3 毫秒),然后进入睡眠状态。如果OMP_WAIT_POLICY设置为active,则忙碌等待时间增加到 30000000000 次旋转(5 分钟,100000 次旋转/秒)。您可以通过设置GOMP_SPINCOUNT可变为繁忙周期的数量(libgomp假设大约 100000 次旋转/毫秒,但它可能会因 CPU 的不同而变化 5 倍)。您可以像这样完全禁用睡眠:

OMP_WAIT_POLICY=active GOMP_SPINCOUNT=infinite OMP_NUM_THREADS=... ./program

这会以某种方式改善线程团队的启动时间,但会以 CPU 时间为代价,因为空闲线程不会空闲,而是忙于等待。

为了消除开销,您应该以对 OpenMP 更友好的方式重写您的程序。您的示例代码可以这样重写:

int main()
{
#define steps (100000)
   #pragma omp parallel
   {
      for (int t = 0; t < steps; t++)
      {
         firstParallelLoop();
         secondParallelLoop();
         if (!(t%100))
         {
            #pragma omp master
            checkpoint();
            #pragma omp barrier
         }
      }
   }
}
void firstParallelLoop()
{// In another file.c
   #pragma omp for
   for (int i = 0; i < sizeOfSim; i++)
   {
      //Some atomic floating point ops.
   }
}

注意以下两点:

  • 在主程序中插入一个并行区域。这不是一个parallel for虽然。团队中的所有线程都将执行外部循环steps时间。
  • 仅使用使for循环输入firstParallelLoop并行omp for。因此,如果在 OpenMP 并行外部调用,它将作为串行循环执行,而在从并行区域内部调用时,它将作为并行执行。中的循环也应该这样做secondParallelLoop

主循环中的屏障用于确保其他线程在开始下一次迭代之前等待检查点完成。

于 2012-09-27T08:37:07.867 回答