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