0

我有一个可以用psedu代码编写的算法,如下所示:

for(int frame=0;frame <1000;frame++)
{
     Image *img=ReadFrame();
     mat processedImage=processImage(img);
     addtompeg(processedImage);
}

ProcessImage 非常耗时,大约需要 30 秒。ReadFrame 和 AddToMpeg 并不慢,但它们需要按顺序完成(否则,Fame 2 可能会在第 1 帧之前添加到输出中)。

如何使用 OpenMP 并行化它?

我正在使用 opencv 来阅读框架和添加 tompeg。

4

2 回答 2

2

从技术上讲,在 OpenMP 中,您可以for使用该子句按照与程序顺序执行相同的顺序执行循环的一部分(请参阅此处ordered的第 2.8.7 节)。无论如何,我不建议使用此条款,原因有两个:

  1. 一个线程不能在同一个循环中执行多个有序区域(这似乎不是你的情况)
  2. 在许多实现中,ordered循环的行为很像顺序循环,对性能有不利影响

因此,在您的情况下,我建议您展开循环:

Image * img           [chunk];
mat     processedImage[chunk];
/* ... */
for(int frame = 0; frame < nframes; frame += chunk) {

  #pragma omp single
  { /* Frames are read in sequential order */
    for( int ii = frame; ii < frame + chunk; ii++) {
       img[ii%chunk] = ReadFrame();
    }
  } /* Implicit barrier here */
  #pragma omp for
  for( int ii = frame; ii < frame + chunk; ii++) {
       processedImage[ii%chunk] = processImage(img[ii%chunk]); /* Images are processed in parallel */
  } /* Implicit barrier here */
  #pragma omp single
  { /* Frames are added to mpeg sequential order */
    for( int ii = frame; ii < frame + chunk; ii++) {
     addtompeg(processedImage[ii%chunk]);
    }
  } /* Implicit barrier here */
}

的值chunk主要取决于对内存的考虑。如果你认为内存不会有问题,那么你可以完全去掉外循环,让内循环 from 0to nframes

当然,必须注意正确管理外循环的其余部分(我没有在代码段中显示)。

于 2013-05-26T09:24:12.300 回答
1

基于 Massimiliano 的分块思想,一个更优雅的解决方案是使用 OpenMP 3.0 及更高版本的显式任务机制(这意味着它不能与 Visual Studio 的 C++ 编译器一起使用):

const int nchunks = 10;

#pragma omp parallel
{
   #pragma omp single
   {
      mat processedImage[nchunks];

      for (int frame = 0; frame < nframes; frame++)
      {
         Image *img = ReadFrame();

         #pragma omp task shared(processedImage)
         {
            processedImage[frame % nchunks] = processImage(img);
            disposeImage(img);
         }

         // nchunks frames read or the last frame reached
         if ((1 + frame) % nchunks == 0 || frame == nframes-1)
         {
            #pragma omp taskwait

            int chunks = 1 + frame % nchunks;
            for (int i = 0; i < chunks; i++)
               addtompeg(processedImage[i]);
         }
      }
   }
}

代码可能看起来很别扭,但它在概念上非常简单。如果不是 OpenMP 结构,它就像一个串行代码,在将nchunks处理过的帧循环添加到输出 MPEG 文件之前缓冲处理过的帧。魔法发生在这段代码中:

#pragma omp task shared(processedImage)
{
   processedImage[frame % nchunks] = processImage(img);
   disposeImage(img);
}

这将创建一个新的 OpenMP 任务,该任务执行块中的两行代码。img并且frame是按值捕获的,即它们是firstprivate,因此不必img是指针数组。生产者任务赋予任务的所有权,img因此任务必须负责处理图像对象。这里重要的是ReadFrame()在单独的缓冲区中分配每个帧,并且每次都不会重用一些内部存储器(我从未使用过 OpenCV,我不知道是否是这种情况)。任务由在某个任务调度点等待的空闲线程排队并执行。构造末尾的隐式屏障single就是这样一个调度点,因此剩余的线程将开始执行任务。一旦那个nchunk帧已被读取或已到达输入的末尾,生产者线程等待所有排队的任务被处理(这就是它的taskwait目的),然后简单地将块写入输出。

选择适当的值nchunks很重要,否则某些线程可能最终会空闲。如果ReadFrameaddtompeg相对较快,即读取和写入num_threads帧花费的时间少于processImage,那么nchunks应该是线程数的精确倍数。如果processImage可能需要不同的时间,那么您需要设置一个非常大的值nchunks以防止负载不平衡。在这种情况下,我宁愿尝试并行化processImage并保持处理循环串行。

于 2013-05-26T12:38:32.240 回答