openMP支持的编程语言包括C语言、C++和Fortran,支持OpenMP的编译器包括Sun Studio,Intel Compiler,Microsoft Visual Studio,GCC。我使用的是Microsoft Visual Studio 2008,CPU为Intel i5 四核,首先讲一下在Microsoft Visual Studio 2008上openMP的配置。非常简单,总共分2步:
(1) 新建一个工程。这个不再多讲。
(2) 建立工程后,点击 菜单栏->Project->Properties,弹出菜单里,点击 Configuration Properties->C/C++->Language->OpenMP Support,在下拉菜单里选择Yes。
至此配置结束。下面我们通过一个小例子来说明openMP的易用性。这个例子是 有一个简单的test()函数,然后在main()里,用一个for循环把这个test()函数跑8遍。
编译运行后,打印出来的耗时为:1.971秒。下面我们用一句话把上面代码变成多核运行。
编译运行后,打印出来的耗时为:0.546秒,几乎为上面时间的1/4。
由此我们可以看到openMP的简单易用。在上面的代码里,我们一没有额外include头文件,二没有额外link库文件,只是在for循环前加了一句#pragma omp parallel for。而且这段代码在单核机器上,或者编译器没有将openMP设为Yes的机器上编译也不会报错,将自动忽略#pragma这行代码,然后按照传统单核串行的方式编译运行!我们唯一要多做的一步,是从C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\x86\Microsoft.VC90.OPENMP和C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\Debug_NonRedist\x86\Microsoft.VC90.DebugOpenMP目录下分别拷贝vcomp90d.dll和vcomp90.dll文件到工程文件当前目录下。
对上面代码按照我的理解做个简单的剖析。
当编译器发现#pragma omp parallel for后,自动将下面的for循环分成N份,(N为电脑CPU核数),然后把每份指派给一个核去执行,而且多核之间为并行执行。下面的代码验证了这种分析。
会发现控制台打印出了0 3 4 5 8 9 6 7 1 2。注意:因为每个核之间是并行执行,所以每次执行时打印出的顺序可能都是不一样的。
下面我们来了谈谈竞态条件(race condition)的问题,这是所有多线程编程最棘手的问题。该问题可表述为,当多个线程并行执行时,有可能多个线程同时对某变量进行了读写操作,从而导致不可预知的结果。比如下面的例子,对于包含10个整型元素的数组a,我们用for循环求它各元素之和,并将结果保存在变量sum里。
#include <iostream> int main() { int sum = 0; int a[10] = {1,2,3,4,5,6,7,8,9,10}; #pragma omp parallel for for (int i=0;i<10;i++) sum = sum + a[i]; std::cout<<"sum: "<<sum<<std::endl; return 0; }
那么用openMP怎么实现并行数组求和呢?下面我们先给出一个基本的解决方案。该方案的思想是,首先生成一个数组sumArray,其长度为并行执行的线程的个数(默认情况下,该个数等于CPU的核数),在for循环里,让各个线程更新自己线程对应的sumArray里的元素,最后再将sumArray里的元素累加到sum里,代码如下
#include <iostream> #include <omp.h> int main(){ int sum = 0; int a[10] = {1,2,3,4,5,6,7,8,9,10}; int coreNum = omp_get_num_procs();//获得处理器个数 int* sumArray = new int[coreNum];//对应处理器个数,先生成一个数组 for (int i=0;i<coreNum;i++)//将数组各元素初始化为0 sumArray[i] = 0; #pragma omp parallel for for (int i=0;i<10;i++) { int k = omp_get_thread_num();//获得每个线程的ID sumArray[k] = sumArray[k]+a[i]; } for (int i = 0;i<coreNum;i++) sum = sum + sumArray[i]; std::cout<<"sum: "<<sum<<std::endl; return 0; }
上面的代码虽然达到了目的,但它产生了较多的额外操作,比如要先生成数组sumArray,最后还要用一个for循环将它的各元素累加起来,有没有更简便的方式呢?答案是有,openMP为我们提供了另一个工具,归约(reduction),见下面代码:
#include <iostream> int main(){ int sum = 0; int a[10] = {1,2,3,4,5,6,7,8,9,10}; #pragma omp parallel for reduction(+:sum) for (int i=0;i<10;i++) sum = sum + a[i]; std::cout<<"sum: "<<sum<<std::endl; return 0; }
reduction虽然很方便,但它只支持一些基本操作,比如+,-,*,&,|,&&,||等。有些情况下,我们既要避免race condition,但涉及到的操作又超出了reduction的能力范围,应该怎么办呢?这就要用到openMP的另一个工具,critical。来看下面的例子,该例中我们求数组a的最大值,将结果保存在max里。
#include <iostream> int main(){ int max = 0; int a[10] = {11,2,33,49,113,20,321,250,689,16}; #pragma omp parallel for for (int i=0;i<10;i++) { int temp = a[i]; #pragma omp critical { if (temp > max) max = temp; } } std::cout<<"max: "<<max<<std::endl; return 0; }
很明显,读入图像,提取特征点与特征描述子这部分可以改为并行执行,修改如下:
#include "opencv2/highgui/highgui.hpp" #include "opencv2/features2d/features2d.hpp" #include <iostream> #include <vector> #include <omp.h> int main( ){ int imNum = 2; std::vector<cv::Mat> imVec(imNum); std::vector<std::vector<cv::KeyPoint>>keypointVec(imNum); std::vector<cv::Mat> descriptorsVec(imNum); cv::SurfFeatureDetector detector( 400 ); cv::SurfDescriptorExtractor extractor; cv::BruteForceMatcher<cv::L2<float> > matcher; std::vector< cv::DMatch > matches; char filename[100]; double t1 = omp_get_wtime( ); #pragma omp parallel for for (int i=0;i<imNum;i++){ sprintf(filename,"rgb%d.jpg",i); imVec[i] = cv::imread( filename, CV_LOAD_IMAGE_GRAYSCALE ); detector.detect( imVec[i], keypointVec[i] ); extractor.compute( imVec[i],keypointVec[i],descriptorsVec[i]); std::cout<<"find "<<keypointVec[i].size()<<"keypoints in im"<<i<<std::endl; } double t2 = omp_get_wtime( ); std::cout<<"time: "<<t2-t1<<std::endl; matcher.match( descriptorsVec[0], descriptorsVec[1], matches ); cv::Mat img_matches; cv::drawMatches( imVec[0], keypointVec[0], imVec[1], keypointVec[1], matches, img_matches ); cv::namedWindow("Matches",CV_WINDOW_AUTOSIZE); cv::imshow( "Matches", img_matches ); cv::waitKey(0); return 1; }
在上面代码中,为了改成适合#pragma omp parallel for执行的方式,我们用了STL的vector来分别存放两幅图像、特征点与特征描述子,但在某些情况下,变量可能不适合放在vector里,此时应该怎么办呢?这就要用到openMP的另一个工具,section,代码如下:
上面代码中,我们首先用#pragma omp parallel sections将要并行执行的内容括起来,在它里面,用了两个#pragma omp section,每个里面执行了图像读取、特征点与特征描述子提取。将其简化为伪代码形式即为:
意思是:parallel sections里面的内容要并行执行,具体分工上,每个线程执行其中的一个section,如果section数大于线程数,那么就等某线程执行完它的section后,再继续执行剩下的section。在时间上,这种方式与人为用vector构造for循环的方式差不多,但无疑该种方式更方便,而且在单核机器上或没有开启openMP的编译器上,该种方式不需任何改动即可正确编译,并按照单核串行方式执行。
以上分享了这两天关于openMP的一点学习体会,其中难免有错误,欢迎指正。另外的一点疑问是,看到各种openMP教程里经常用到private,shared等来修饰变量,这些修饰符的意义和作用我大致明白,但在我上面所有例子中,不加这些修饰符似乎并不影响运行结果,不知道这里面有哪些讲究。
在写上文的过程中,参考了包括以下两个网址在内的多个地方的资源,不再一 一列出,在此一并表示感谢。
http://blog.csdn.net/drzhouweiming/article/details/4093624
http://software.intel.com/zh-cn/articles/more-work-sharing-with-openmp
转载:http://www.cnblogs.com/yangyangcv/archive/2012/03/23/2413335.html