OpenMP:为啥这个应用程序有时会扩展?

Posted

技术标签:

【中文标题】OpenMP:为啥这个应用程序有时会扩展?【英文标题】:OpenMP: why this application scale sometimes?OpenMP:为什么这个应用程序有时会扩展? 【发布时间】:2016-11-08 01:45:22 【问题描述】:

我正在尝试在Intel® Core™ i5-6500 CPU @ 3.20GHz × 4 上使用 OpenMP 加速 OpenCV SIFT 算法。您可以在sift.cpp找到代码。

最昂贵的部分是描述符计算,特别是:

static void calcDescriptors(const std::vector<Mat>& gpyr, const std::vector<KeyPoint>& keypoints,
                            Mat& descriptors, int nOctaveLayers, int firstOctave )

    int d = SIFT_DESCR_WIDTH, n = SIFT_DESCR_HIST_BINS;
    for( size_t i = 0; i < keypoints.size(); i++ )
    
        KeyPoint kpt = keypoints[i];
        int octave, layer;
        float scale;
        unpackOctave(kpt, octave, layer, scale);
        CV_Assert(octave >= firstOctave && layer <= nOctaveLayers+2);
        float size=kpt.size*scale;
        Point2f ptf(kpt.pt.x*scale, kpt.pt.y*scale);
        const Mat& img = gpyr[(octave - firstOctave)*(nOctaveLayers + 3) + layer];

        float angle = 360.f - kpt.angle;
        if(std::abs(angle - 360.f) < FLT_EPSILON)
            angle = 0.f;
        calcSIFTDescriptor(img, ptf, angle, size*0.5f, d, n, descriptors.ptr<float>((int)i));
    

此函数的串行版本平均占用52 ms

这个for 具有很高的粒度:它被执行了604 次(即 keypoints.size() )。 for 内部的主要耗时组件是 calcSIFTDescriptor,它占用了大部分循环时间计算,平均占用 105 us,但经常会占用 200us50us

但是,我们非常幸运:每个 for 循环之间没有依赖关系,所以我们可以添加:

#pragma omp parallel for schedule(dynamic,8)

并获得初始加速。引入了dynamic 选项,因为它的性能似乎比static 好一点(不知道为什么)。

问题在于它真的很不稳定并且无法扩展。这是在并行模式下计算函数所需的时间:

25ms 43ms 32ms 15ms 27ms 53ms 21ms 24ms

您只能在达到四核系统的最佳加速比时看到 (15ms)。大多数情况下,我们会达到最佳加速比的一半:四核系统中的25ms 只是理论最佳加速比的一半。

为什么会这样?我们该如何改进呢?

更新: 正如 cmets 中所建议的,我尝试使用更大的数据集。使用一个巨大的图像,串行版本使用13574ms 来计算描述符,而并行版本3704ms 使用与之前相同的四核。好多了:即使它不是最好的理论结果,它实际上也可以很好地扩展。但实际上问题仍然存在,因为之前的结果是从典型图像中获得的。

更新 1: 正如评论所建议的,我尝试在“热模式”下的执行之间没有任何间隔进行基准测试(有关更多详细信息,请参阅评论)。更频繁地获得更好的结果,但仍然有很多变化。这是在热模式下运行 100 次的时间(以毫秒为单位):

43 42 14 26 14 43 13 26 15 51 15 20 14 40 34 15 15 31 15 22 14 21 17 15 14 27 14 16 14 22 14 22 15 15 14 43 16 16 15 28 14 24 14 36 15 32 13 21 14 23 14 15 13 26 15 35 13 32 14 36 14 34 15 40 28 14 14 15 15 35 15 22 14 17 15 23 14 24 17 16 14 35 14 29 14 25 14 32 14 28 14 34 14 30 22 14 15 24 14 31

你可以看到很多好的结果(14ms15ms),但也有很多糟糕的结果(&gt;40ms)。平均值为22ms 注意在顺序模式下最多没有4ms 的变化:

52 54 52 52 51 52 52 53 53 52 53 51 52 53 53 54 53 53 53 53 54 53 54 54 53 53 53 52 53 52 51 52 52 53 54 54 54 55 55 55 54 54 54 53 53 52 52 52 51 52 54 53 54 54 54 55 54 54 52 55 52 52 52 51 52 51 52 52 51 51 52 52 53 53 53 53 55 54 55 54 54 54 55 52 52 52 51 51 52 51 51 51 52 53 53 54 53 54 53 55

更新 2:

我注意到“热模式”基准测试期间的每个 CPU 利用率都是非常随机的,而且它从未达到超过 80%,如下图所示:

相反,下图显示了我通过make -j4 编译 OpenCV 时的 CPU 利用率。如您所见,它更稳定并且几乎 100% 使用它:

我认为这是第一个图像的变化是正常的,因为我们多次执行相同的短程序,这比一个大程序更不稳定。我不明白的是为什么我们的 CPU 利用率永远不会超过 80%

【问题讨论】:

你能在更大的数据集上给出一些测试结果吗?尝试让它持续几秒钟,以更好地了解不受后台进程影响或完全由线程创建开销决定的加速。 您使用的是什么编译器和操作系统?你在绑定线程吗?将环境中的OMP_PROCBIND 设置为true。此外,请确保没有其他程序占用 CPU 时间。诸如 Intel VTune Amplifier(商业许可)或 Sun Studio 的线程分析器之类的工具,甚至 Score-P 都可以让您深入了解线程将时间花在哪里。 @Micka gettimeofday() 在 Linux 中非常常见且多核安全 此工具可用于您的操作系统吗? developers.redhat.com/blog/2014/03/10/… 正如之前多次提到的,这个问题最好在适当的工具支持下进行调查。理想情况下,您需要一个了解线程并捕获不同迭代动态的工具,就像 Hristo Iliev 提到的那样。不幸的是,没有一个可重复的小例子,我们只能猜测...... 【参考方案1】:

我强烈建议您使用一些性能工具,例如 Paraver (http://www.bsc.es/paraver)、TAU (http://www.cs.uoregon.edu/research/tau/home.php) Vampir (https://tu-dresden.de/die_tu_dresden/zentrale_einrichtungen/zih/forschung/projekte/vampir) 甚至 Intel 的 Vtune (https://software.intel.com/en-us/intel-vtune-amplifier-xe)。

这些工具将帮助您了解线程在哪里花费其周期。使用它们,您可以发现应用程序是否不平衡(通过 IPC 或指令),是否存在由于内存带宽或错误共享问题而导致的任何限制,以及许多其他问题。

【讨论】:

以上是关于OpenMP:为啥这个应用程序有时会扩展?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 OpenMP 程序只在一个线程中运行

请看看则个fortran结合openmp并行程序,为啥老出错?

为啥使用openmp时会间歇性出现“fatal error C1001”错误?

java方法的参数 为啥有时会加上final关键字

为啥 OCaml 有时需要 eta 扩展?

为啥这段代码(在 Matlab 的 MEX 文件中使用 OpenMP)给出不同的结果?