用OpenMP加速你的程序[转]

      最近在看多核编程。简单来说,由于现在电脑CPU一般都有两个核,4核与8核的CPU也逐渐走入了寻常百姓家,传统的单线程编程方式难以发挥多核 CPU的强大功能,于是多核编程应运而生。按照我的理解,多核编程可以认为是对多线程编程做了一定程度的抽象,提供一些简单的API,使得用户不必花费太多精力来了解多线程的底层知识,从而提高编程效率。这两天关注的多核编程的工具包括openMP和TBB。按照目前网上的讨论,TBB风头要盖过 openMP,比如openCV过去是使用openMP的,但从2.3版本开始抛弃openMP,转向TBB。但我试下来,TBB还是比较复杂的,相比之下,openMP则非常容易上手。因为精力和时间有限,没办法花费太多时间去学习TBB,就在这里分享下这两天学到的openMP的一点知识,首先从openMP的基本知识入手,最后针对图像特征点提取给出一个结合openCV的具体的例子。
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遍。

#include <iostream>
#include <time.h>
void test()
{int a = 0;for (int i=0;i<100000000;i++)a++;
}
int main()
{clock_t t1 = clock();for (int i=0;i<8;i++)test();clock_t t2 = clock();std::cout<<"time: "<<t2-t1<<std::endl;
}


编译运行后,打印出来的耗时为:1.971秒。下面我们用一句话把上面代码变成多核运行。

#include <iostream>
#include <time.h>
void test()
{int a = 0;for (int i=0;i<100000000;i++)a++;
}
int main()
{clock_t t1 = clock();#pragma omp parallel forfor (int i=0;i<8;i++)test();clock_t t2 = clock();std::cout<<"time: "<<t2-t1<<std::endl;
}


编译运行后,打印出来的耗时为: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核数),然后把每份指派给一个核去执行,而且多核之间为并行执行。下面的代码验证了这种分析。

#include <iostream>
int main()
{
#pragma omp parallel forfor (int i=0;i<10;i++)std::cout<<i<<std::endl;return 0;
}

会发现控制台打印出了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 forfor (int i=0;i<10;i++)sum = sum + a[i];std::cout<<"sum: "<<sum<<std::endl;return 0;
}

如果我们注释掉#pragma omp parallel for,让程序先按照传统串行的方式执行,很明显,sum = 55。但按照并行方式执行后,sum则会变成其他值,比如在某次运行过程中,sum = 49。其原因是,当某线程A执行sum = sum + a[i]的同时,另一线程B正好在更新sum,而此时A还在用旧的sum做累加,于是出现了错误。
那么用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++)//将数组各元素初始化为0sumArray[i] = 0;
#pragma omp parallel forfor (int i=0;i<10;i++){int k = omp_get_thread_num();//获得每个线程的IDsumArray[k] = sumArray[k]+a[i];}for (int i = 0;i<coreNum;i++)sum = sum + sumArray[i];std::cout<<"sum: "<<sum<<std::endl;return 0;
}

需要注意的是,在上面代码里,我们用omp_get_num_procs()函数来获取处理器个数,用omp_get_thread_num()函数来获得每个线程的ID,为了使用这两个函数,我们需要include <omp.h>。
上面的代码虽然达到了目的,但它产生了较多的额外操作,比如要先生成数组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;
}

上面代码里,我们在#pragma omp parallel for 后面加上了 reduction(+:sum),它的意思是告诉编译器:下面的for循环你要分成多个线程跑,但每个线程都要保存变量sum的拷贝,循环结束后,所有线程把自己的sum累加起来作为最后的输出。
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 forfor (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;
}

上例中,for循环还是被自动分成N份来并行执行,但我们用#pragma omp critical将 if (temp > max) max = temp 括了起来,它的意思是:各个线程还是并行执行for里面的语句,但当你们执行到critical里面时,要注意有没有其他线程正在里面执行,如果有的话,要等其他线程执行完再进去执行。这样就避免了race condition问题,但显而易见,它的执行速度会变低,因为可能存在线程等待的情况。
有了以上基本知识,对我来说做很多事情都足够了。下面我们来看一个具体的应用例,从硬盘读入两幅图像,对这两幅图像分别提取特征点,特征点匹配,最后将图像与匹配特征点画出来。编译该例需要opencv,我用的版本是2.3.1,关于 opencv的安装与配置不在此介绍。我们首先来看传统串行编程的方式。

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/features2d/features2d.hpp"
#include <iostream>
#include <omp.h>
int main( ){cv::SurfFeatureDetector detector( 400 );    cv::SurfDescriptorExtractor extractor;cv::BruteForceMatcher<cv::L2<float> > matcher;std::vector< cv::DMatch > matches;cv::Mat im0,im1;std::vector<cv::KeyPoint> keypoints0,keypoints1;cv::Mat descriptors0, descriptors1;double t1 = omp_get_wtime( );//先处理第一幅图像im0 = cv::imread("rgb0.jpg", CV_LOAD_IMAGE_GRAYSCALE );detector.detect( im0, keypoints0);extractor.compute( im0,keypoints0,descriptors0);std::cout<<"find "<<keypoints0.size()<<"keypoints in im0"<<std::endl;//再处理第二幅图像im1 = cv::imread("rgb1.jpg", CV_LOAD_IMAGE_GRAYSCALE );detector.detect( im1, keypoints1);extractor.compute( im1,keypoints1,descriptors1);std::cout<<"find "<<keypoints1.size()<<"keypoints in im1"<<std::endl;double t2 = omp_get_wtime( );std::cout<<"time: "<<t2-t1<<std::endl;matcher.match( descriptors0, descriptors1, matches );cv::Mat img_matches;cv::drawMatches( im0, keypoints0, im1, keypoints1, matches, img_matches ); cv::namedWindow("Matches",CV_WINDOW_AUTOSIZE);cv::imshow( "Matches", img_matches );cv::waitKey(0);return 1;
}

很明显,读入图像,提取特征点与特征描述子这部分可以改为并行执行,修改如下:

#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 forfor (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;
}

两种执行方式做比较,时间为:2.343秒v.s. 1.2441秒
在上面代码中,为了改成适合#pragma omp parallel for执行的方式,我们用了STL的vector来分别存放两幅图像、特征点与特征描述子,但在某些情况下,变量可能不适合放在vector里,此时应该怎么办呢?这就要用到openMP的另一个工具,section,代码如下:

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/features2d/features2d.hpp"
#include <iostream>
#include <omp.h>
int main( ){cv::SurfFeatureDetector detector( 400 );    cv::SurfDescriptorExtractor extractor;cv::BruteForceMatcher<cv::L2<float> > matcher;std::vector< cv::DMatch > matches;cv::Mat im0,im1;std::vector<cv::KeyPoint> keypoints0,keypoints1;cv::Mat descriptors0, descriptors1;double t1 = omp_get_wtime( );
#pragma omp parallel sections{
#pragma omp section{std::cout<<"processing im0"<<std::endl;im0 = cv::imread("rgb0.jpg", CV_LOAD_IMAGE_GRAYSCALE );detector.detect( im0, keypoints0);extractor.compute( im0,keypoints0,descriptors0);std::cout<<"find "<<keypoints0.size()<<"keypoints in im0"<<std::endl;}
#pragma omp section{std::cout<<"processing im1"<<std::endl;im1 = cv::imread("rgb1.jpg", CV_LOAD_IMAGE_GRAYSCALE );detector.detect( im1, keypoints1);extractor.compute( im1,keypoints1,descriptors1);std::cout<<"find "<<keypoints1.size()<<"keypoints in im1"<<std::endl;}}double t2 = omp_get_wtime( );std::cout<<"time: "<<t2-t1<<std::endl;matcher.match( descriptors0, descriptors1, matches );cv::Mat img_matches;cv::drawMatches( im0, keypoints0, im1, keypoints1, matches, img_matches ); cv::namedWindow("Matches",CV_WINDOW_AUTOSIZE);cv::imshow( "Matches", img_matches );cv::waitKey(0);return 1;
}

上面代码中,我们首先用#pragma omp parallel sections将要并行执行的内容括起来,在它里面,用了两个#pragma omp section,每个里面执行了图像读取、特征点与特征描述子提取。将其简化为伪代码形式即为:

#pragma omp parallel sections
{#pragma omp section{function1();}#pragma omp section{function2();}
}

意思是:parallel sections里面的内容要并行执行,具体分工上,每个线程执行其中的一个section,如果section数大于线程数,那么就等某线程执行完它的 section后,再继续执行剩下的section。在时间上,这种方式与人为用vector构造for循环的方式差不多,但无疑该种方式更方便,而且在单核机器上或没有开启openMP的编译器上,该种方式不需任何改动即可正确编译,并按照单核串行方式执行。
以上分享了这两天关于openMP的一点学习体会,其中难免有错误,欢迎指正。另外的一点疑问是,看到各种openMP教程里经常用到 private,shared等来修饰变量,这些修饰符的意义和作用我大致明白,但在我上面所有例子中,不加这些修饰符似乎并不影响运行结果,不知道这里面有哪些讲究。
在写上文的过程中,参考了包括以下两个网址在内的多个地方的资源,不再一 一列出,在此一并表示感谢。
http://blog.csdn.net/drzhouweiming/arti ... ls/4093624
http://software.intel.com/zh-cn/article ... ith-openmp
原文发表于我的博客:http://www.cnblogs.com/yangyangcv/archive/2012/03/23/2413335.html,欢迎访问~~

转载于:https://www.cnblogs.com/CVArt/archive/2012/06/01/2529824.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/434498.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

错误./hello: error while loading shared libraries: libQtGui.so.4: cannot open shared object file:

之前一直想在ARM 上跑qt&#xff0c;但都出现错误&#xff1a; ./hello: error while loading shared libraries: libQtGui.so.4: cannot open shared object file: No such file or directory 这主要是ARM 上的运行环境设置不当&#xff1a; 我用的是飞凌的6410 环境变量设…

linux 指令引用变量,Linux之变量引用与命令替换

在bash脚本编写中&#xff0c;我们时常需要引用变量与替换命令&#xff0c;为规范操作&#xff0c;现对其做简单的总结说明。引用引用就是指将字符串用引用符号括起来&#xff0c;以防止特殊字符被shell脚本解释为其他意义。引用时屏蔽特殊字符的特殊意义&#xff0c;而将其解释…

有些垃圾网站转载都不会

有时会看到我的文章被转载&#xff0c;只要保留作者信息和原文链接&#xff0c;并且忠实于原文都是很欢迎的。这里的忠实原文应该是最基本的了吧&#xff0c;转载嘛&#xff0c;最简单的也就是拷贝粘贴吧&#xff0c;可发现有些垃圾网站&#xff0c;连拷贝粘贴都做不好&#xf…

移植tslib到开发板及部分问题解决

qt的tslib的具体移植步骤和过程就不多讲了&#xff0c;我说说我按照手册移植好tslib后在开发板运行提示的错误以及我的解决方法&#xff0c;当然每个人的提示可能一样&#xff0c;解决方法就不一样&#xff0c;我也是个初学者&#xff0c;所以方法仅供大家参考&#xff01;问题…

Content Provider 基础 之URI

Keywords: content provider, URIContent Provider这个东西在Android平台上是最常用的共享数据的方法&#xff08;似乎应用程序之间共享数据也只有这种方法吧&#xff0c;待求证&#xff09;。虽然常用&#xff0c;但是这个东 西要理解透彻还是要先掌握一些基础的。URI就是Cont…

linux shell 原理,linux下shell的工作原理

linux下shell的工作原理2009-12-8 10:19:53 出处&#xff1a;https://www.yqdown.comshell是用户和Linux操作系统之间的接口。Linux中有多种shell&#xff0c;其中缺省运用的是Bash。本章讲述了shell的工作原理&#xff0c;shell的种类&#xff0c;shell的一般操作及Bash的特…

解决SerMyAdmin无法登陆的问题

1. 首先确认默认的用户名和密码&#xff0c;因为每个版本的SerMyAdmin的默认用户名密码可能不同&#xff0c;根据 《Building Telephony Systems with OpenSIPS 1.6》这本书上所说的SerMyAdmin的用户名和密码是 adminsetup和secret。但是从我的SerMyAdmin安装包里的MySql数据…

oracle.cmd

--更改列顺序 SELECT * FROM ALL_OBJECTS WHERE OWNER TDDJ AND OBJECT_NAMELSZD_JZDZB AND OBJECT_TYPETABLE--54529 SELECT OBJ#,COL#,NAME FROM SYS.COL$ WHERE OBJ# 57689; UPDATE SYS.COL$ SET COL# 9 WHERE OBJ# 57689 AND NAME XMBM; UPDATE SYS.COL$ SET COL# 7 WHE…

Linux部署动态网页,linux构建动态WEB服务器安装篇

linux构建动态WEB服务器安装篇发布时间&#xff1a;2008-09-18 08:50:01 作者&#xff1a;佚名 我要评论基本配置安装web服务器:httpd-2.X.X先优化吧根据CPU设置变量#export CFLAGS”-O2 -marchpentium4 -pipe” && CXXFLAGS$CFLAGS“-march”选项表示为特定的cpu…

sip事务与对话

一个事务通常由一个请求开始&#xff0c;由一个响应码&#xff08;a response code&#xff09;结束。VIA头域中的branch参数用来标识一个事务。 对话可以是开始于一个INVITE事务&#xff0c;结束于一个BYE事务。一个对话由FROM&#xff0c;TO和CALL&#xff0d;ID头域的结合所…

[转]Win7 系统安装VS2008没反应 点击安装一闪就没有反应 .

原文地址&#xff1a;http://blog.csdn.net/mingjie_520/article/details/6757960 Win7 系统上用虚拟光驱安装 VS2008 没反应&#xff0c;安装不启解决方案VS2008 ISO境像文件&#xff0c;解开来安装的话&#xff0c;也没有问题&#xff0c;不过真的很慢&#xff0c;解出又占空…

apt-get的更新源

1、先备份 sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup &#xff08;备份下当前的源列表&#xff0c;有备无患嘛.&#xff09; 2、修改更新源 sudo gedit /etc/apt/sources.list &#xff08;打开源列表文件&#xff09; 3、修改 deb http://mirrors.sohu.com…

电脑用linux命令大全,电脑操作时常用的一些Linux命令

电脑操作时常用的一些Linux命令1、修改密码&#xff1a;passwd用户名2、下载jdk-6u25-linux-i586.bin3、运行(cd进入某一目录后&#xff0c;/usr/jdk-6u25-linux-i586.bin解压缩操作)sh jdk-6u25-linux-i586.bin4、到opadmin home目录下改环境变量vi .bash_profile编辑用户主目…

在WIN7下硬盘安装XP双系统

在WIN7下硬盘安装XP双系统 对安装过程有疑问的朋友们请在雨林木风论坛 请教其他高手吧&#xff0c;他们很热心的&#xff01;对于有些人提的许多很白痴的问题的确爱莫能助了。。 很多朋友因为WIN7的美观界面和方便的操作而放弃了xp&#xff0c;但是用过一段时间的WIN7后会发现…

px、dp和sp,这些单位有什么区别?

相信每个Android新手都会遇到这个问题&#xff0c;希望这篇帖子能让你不再纠结。 px&#xff1a; 即像素&#xff0c;1px代表屏幕上一个物理的像素点&#xff1b; px单位不被建议使用&#xff0c;因为同样100px的图片&#xff0c;在不同手机上显示的实际大小可能不同&#x…

linux 例行性工作,Linux例行性工作

Linux例行性工作Linux:例行性工作什么是例行性工作&#xff1f;其实对于例行性工作而言就是在当前环境下周期性进行的必须工作。在Linux下&#xff0c;有2种例行性工作种类&#xff1a;一种是突发性&#xff0c;做完以后就没有的那种at:at是个可以处理仅执行一次就结束调度的命…

socket 编程入门教程(三)TCP原理:5、TCP的三次握手(three-way handshake)

前面3个小节介绍了socket机制对TCP协议三次握手的实现&#xff0c;需要强调的是&#xff0c;与协议独立于实现类似&#xff0c;TCP的三次握手是独立于socket体系的理论。在TCP协议中&#xff0c;三次握手是通过3个TCP格式的IP数据报来实现的。TCP格式的IP数据报中包含着TCP首部…

Android串口通信实例分析【附源码】

Android 串口通信实例分析&#xff0c;用的时开源的android-serialport-api 这个是用android ndk实现的串口通信&#xff0c;我把他做了一个简化&#xff0c;适合于一般的程序的串口通信移植&#xff0c;欢迎拍砖&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e…

linux 下搭建yolov3错误,Ubuntu20.04+cuda11.1+yolo3 目标检测 深度学习系统 真正从0搭建 包含各类可能遇到的错误...

本篇文章包含内容较多&#xff0c;请参照目录浏览,在每一部分结束或,有该部分可能遇到的问题0、开始本教程前请先备份电脑中的重要文件&#xff01;&#xff01;&#xff01;&#xff01;一、Ubuntu20.04 搭建更详细的内容请参照该博客&#xff1a;https://www.cnblogs.com/mas…

怎么配置SQLServer2005以允许远程连接

默认情况下,SQLServerExpressEdition 2005和SQL Server 2005开发版不允许远程连接。  要允许远程连接SQL Server 2005 ,需要完成以下步骤&#xff1a;  在SQLServer 实例上启用远程连接。  打开服务器 SQLBrowser 服务。  配置防火墙以允许SQLBrowser 服务和SQLServer服…