在c ++中执行此操作的最快方法是什么(使用OpenMP)

时间:2013-05-25 21:51:07

标签: c++ opencv openmp

我有一个algorithem,我可以用psedu代码编写如下:

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

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

如何使用OpenMP并行化它?

我使用opencv来readframe和addtompeg。

2 个答案:

答案 0 :(得分:2)

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

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

    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的值主要取决于对内存的考虑。如果您认为内存不会有问题,那么您可以完全删除外部循环并让内部循环从0转到nframes

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

答案 1 :(得分: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结构,它就像一个串行代码,在将它们循环添加到输出MPEG文件之前缓冲多达nchunks个处理过的帧。魔术发生在这段代码中:

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

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

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