C++ OpenMP 和 gcc 4.8.1 - 并行化循环时的性能问题

Posted

技术标签:

【中文标题】C++ OpenMP 和 gcc 4.8.1 - 并行化循环时的性能问题【英文标题】:C++ OpenMP and gcc 4.8.1 - performance issue when parallelising loops 【发布时间】:2013-10-19 12:48:05 【问题描述】:

我最近开始研究 OpenMP,因为我将从事一些计算量大、成本高的图像分析项目。我使用带有 Intel i7(8 核)和 mingw64 gcc 4.8.1 的 Windows 7。我在 Code::Blocks 中编码并设置了所有内容以便编译和运行它。在我的代码中的几个部分,我将做一些像素级的操作,我认为这将是并行处理的一个很好的候选者。令我惊讶的是,事实证明顺序处理比并行处理快。我为 32 位和 64 位以及两台不同的计算机尝试了不同版本的 gcc(4.7 - 4.8),但我总是遇到相同的性能问题。然后,我尝试使用我在这两台计算机中的一台上的旧 Visual Studio 2008 运行它,我的性能得到了预期的提升。因此,我的问题是 - 为什么我无法使用 gcc 看到相同的效果。是不是我做错了什么?

这是一个最小的工作示例。

#include <omp.h>
#include <cstdlib>
#include <iostream>

int main(int argc, char * argv[])

   /* process a stack of images - set the number to 1000 for testing */
   int imgStack = 1000;

   double start_t = omp_get_wtime();
   for (int img = 0; img < imgStack; img++)
   
      omp_set_num_threads(8);
      #pragma omp parallel for default(none)
      for (int y = 0; y < 1000000000; y++) /* increased the number of pixels to make it worthwhile and to see a difference*/
      
         for (int x = 0; x < 1000000000; x++)
         
            unsigned char pixel[4];
            pixel[0] = 1;
            pixel[1] = 2;
            pixel[2] = 3;
            pixel[3] = 4;

            /* here I would do much more but removed it for testing purposes */

         
      
   
   double end_t = (omp_get_wtime() - start_t) * 1000.0;
   std::cout << end_t << "ms" << std::endl;

   return 0;

在构建日志中我有以下内容

x86_64-w64-mingw32-g++.exe -Wall -O2 -fopenmp -c C:\Code\omptest\main.cpp -o obj\Release\main.o
x86_64-w64-mingw32-g++.exe -o bin\Release\omptest.exe obj\Release\main.o -s C:\mingw-builds\x64-4.8.1-posix-seh-rev5\mingw64\bin\libgomp-1.dll

输出如下

for 1 thread :   43ms
for 8 threads:  594ms

我还尝试关闭优化 (-O0),以防编译器执行一些循环展开。我阅读了关于错误共享问题的信息,因此我将循环中的任何变量都保留为私有,以确保这不是问题。我不擅长分析,所以我不知道下面发生了什么,例如导致所有线程等待的内部锁。

我无法弄清楚我在这里做错了什么。

- 编辑-

感谢大家。在我的真实代码中,我有一个包含 2000 个图像的图像堆栈,每个图像大小为 2000x2000 像素。我试图简化示例,以便每个人都可以轻松地重现该问题,其中我将其简化得太多了,结果导致了其他问题。你们都完全正确。 在我的真实代码中,我使用 Qt 打开和显示我的图像,以及我自己的图像管理器,它加载并遍历堆栈以一次给我一张图像。我认为提供整个示例会太多并且会使事情复杂化(即不提供最低工作示例)。

我将所有变量(imageHeight、imageWidth 等)作为 const 传递,仅将指向我的图像的指针作为共享。最初那是一个指向 QImage 的指针。在循环中,我使用 qtimg->setPixel(...) 设置最终像素值,与 gcc 编译器相比,MSVC 编译器的处理方式似乎不同。最后,我将 QImage 指针替换为指向 unsigned char 数组的指针,这使我的性能得到了预期的提升。

@Hristo Iliev:感谢有关线程池的信息。很高兴知道这一点。

【问题讨论】:

你在你的内部循环中根本没有做任何事情。编译器应该完全优化它,所以你所看到的只是设置线程和分配(不)工作给它们的成本。 如果QImage::setPixel() 使用内部锁,例如为了使操作线程安全,一次从多个线程调用它只会序列化它们的执行。 【参考方案1】:

由于pixels 仅被分配然后从未使用过,整个内部循环被 GCC 的优化器完全删除,-O2 可以通过启用树转储轻松验证:

; Function <built-in> (main._omp_fn.0, funcdef_no=1036, decl_uid=21657, cgraph_uid=256)

<built-in> (void * .omp_data_i)

<bb 2>:
  return;


您所做的是有效地衡量 OpenMP 运行时开销。

使用-O0,所有代码都保留在原位,运行时间随线程数的增加而变化,但我怀疑您是否曾经使用 1000000000 x 1000000000 图像对其进行测试。

【讨论】:

【参考方案2】:

鉴于代码示例,我无法重复您的结果。您必须显示您的真实堆栈大小和图像大小。因为如果工作可以在 5 毫秒内用 1 个线程完成,多线程不会让它更快。启动多个线程会带来很大的开销,尤其是当您启动它们imgStack 次时。

【讨论】:

多年来,GCC、MSVC、Intel 和大多数其他公司都有他们的 OpenMP 运行时使用线程池实现工作线程。只有第一个并行区域是昂贵的。除非以后需要更多线程,否则进入并行区域的更多条目不会像您预期的那样昂贵。

以上是关于C++ OpenMP 和 gcc 4.8.1 - 并行化循环时的性能问题的主要内容,如果未能解决你的问题,请参考以下文章

基于GCC的openMP学习与测试

GCC 8.1.0/MinGW64 编译的 OpenMP 程序崩溃寻找 cygwin.s?

如何在命令行中使用intel c++编译器,并使用openmp和mkl来编译自己的程序,并运算

运行openmp需要安装啥软件?

如何使用 GCC 5.1 和 OpenMP 将工作卸载到 Xeon Phi

使用 OpenMP 4.0 (gcc 4.8.4) 而不是 OpenMP 3.1 (gcc 4.9.2) 时速度会降低