学习OpenCV——OpenMP
转自:http://www.cnblogs.com/yangyangcv/archive/2012/03/23/2413335.html
openMP的一点使用经验
最近在看多核编程。简单来说,由于现在电脑CPU一般都有两个核,4核与8核的CPU也逐渐走入了寻常百姓家,传统的单线程编程方式难以发挥多核CPU的强大功能,于是多核编程应运而生。按照我的理解,多核编程可以认为是对多线程编程做了一定程度的抽象,提供一些简单的API,使得用户不必花费太多精力来了解多线程的底层知识,从而提高编程效率。这两天关注的多核编程的工具包括openMP和TBB。按照目前网上的讨论,TBB风头要盖过openMP,比如openCV过去是使用openMP的,但从2.3版本开始抛弃openMP,转向TBB。但我试下来,TBB还是比较复杂的,相比之下,openMP则非常容易上手。因为精力和时间有限,没办法花费太多时间去学习TBB,就在这里分享下这两天学到的openMP的一点知识,和大家共同讨论。
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 #include <iostream>
2 #include <time.h>
3 void test()
4 {
5 int a = 0;
6 for (int i=0;i<100000000;i++)
7 a++;
8 }
9 int main()
10 {
11 clock_t t1 = clock();
12 for (int i=0;i<8;i++)
13 test();
14 clock_t t2 = clock();
15 std::cout<<"time: "<<t2-t1<<std::endl;
16 }
编译运行后,打印出来的耗时为:1.971秒。下面我们用一句话把上面代码变成多核运行。
1 #include <iostream>
2 #include <time.h>
3 void test()
4 {
5 int a = 0;
6 for (int i=0;i<100000000;i++)
7 a++;
8 }
9 int main()
10 {
11 clock_t t1 = clock();
12 #pragma omp parallel for
13 for (int i=0;i<8;i++)
14 test();
15 clock_t t2 = clock();
16 std::cout<<"time: "<<t2-t1<<std::endl;
17 }
编译运行后,打印出来的耗时为: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里。
如果我们注释掉#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里,代码如下
需要注意的是,在上面代码里,我们用omp_get_num_procs()函数来获取处理器个数,用omp_get_thread_num()函数来获得每个线程的ID,为了使用这两个函数,我们需要include <omp.h>。
上面的代码虽然达到了目的,但它产生了较多的额外操作,比如要先生成数组sumArray,最后还要用一个for循环将它的各元素累加起来,有没有更简便的方式呢?答案是有,openMP为我们提供了另一个工具,归约(reduction),见下面代码:
1 #include <iostream>
2 int main(){
3 int sum = 0;
4 int a[10] = {1,2,3,4,5,6,7,8,9,10};
5 #pragma omp parallel for reduction(+:sum)
6 for (int i=0;i<10;i++)
7 sum = sum + a[i];
8 std::cout<<"sum: "<<sum<<std::endl;
9 return 0;
10 }
上面代码里,我们在#pragma omp parallel for 后面加上了 reduction(+:sum),它的意思是告诉编译器:下面的for循环你要分成多个线程跑,但每个线程都要保存变量sum的拷贝,循环结束后,所有线程把自己的sum累加起来作为最后的输出。
reduction虽然很方便,但它只支持一些基本操作,比如+,-,*,&,|,&&,||等。有些情况下,我们既要避免race condition,但涉及到的操作又超出了reduction的能力范围,应该怎么办呢?这就要用到openMP的另一个工具,critical。来看下面的例子,该例中我们求数组a的最大值,将结果保存在max里。
1 #include <iostream>
2 int main(){
3 int max = 0;
4 int a[10] = {11,2,33,49,113,20,321,250,689,16};
5 #pragma omp parallel for
6 for (int i=0;i<10;i++)
7 {
8 int temp = a[i];
9 #pragma omp critical
10 {
11 if (temp > max)
12 max = temp;
13 }
14 }
15 std::cout<<"max: "<<max<<std::endl;
16 return 0;
17 }
上例中,for循环还是被自动分成N份来并行执行,但我们用#pragma omp critical将 if (temp > max) max = temp 括了起来,它的意思是:各个线程还是并行执行for里面的语句,但当你们执行到critical里面时,要注意有没有其他线程正在里面执行,如果有的话,要等其他线程执行完再进去执行。这样就避免了race condition问题,但显而易见,它的执行速度会变低,因为可能存在线程等待的情况。
有了以上基本知识,对我来说做很多事情都足够了。下面我们来看一个具体的应用例,从硬盘读入两幅图像,对这两幅图像分别提取特征点,特征点匹配,最后将图像与匹配特征点画出来。理解该例子需要一些图像处理的基本知识,我不在此详细介绍。另外,编译该例需要opencv,我用的版本是2.3.1,关于opencv的安装与配置也不在此介绍。我们首先来看传统串行编程的方式。
很明显,读入图像,提取特征点与特征描述子这部分可以改为并行执行,修改如下:
两种执行方式做比较,时间为:2.343秒v.s. 1.2441秒
在上面代码中,为了改成适合#pragma omp parallel for执行的方式,我们用了STL的vector来分别存放两幅图像、特征点与特征描述子,但在某些情况下,变量可能不适合放在vector里,此时应该怎么办呢?这就要用到openMP的另一个工具,section,代码如下:
上面代码中,我们首先用#pragma omp parallel sections将要并行执行的内容括起来,在它里面,用了两个#pragma omp section,每个里面执行了图像读取、特征点与特征描述子提取。将其简化为伪代码形式即为:
1 #pragma omp parallel sections
2 {
3 #pragma omp section
4 {
5 function1();
6 }
7 #pragma omp section
8 {
9 function2();
10 }
11 }
意思是: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
我的是双核,实验后面几个surf检测程序发现提高不多。我觉得是因为这种程序可能需要线程多,并行提升不明显~
学习OpenCV——OpenMP的更多相关文章
- 学习opencv之路(一)
先看一下<学习opencv> 找几个demo 学会相机标定 我做的是单目相机的标定.
- [纯小白学习OpenCV系列]官方例程00:世界观与方法论
2015-11-11 ----------------------------------------------------------------------------------- 其实,写博 ...
- 《学习OpenCV》中求给定点位置公式
假设有10个三维的点,使用数组存放它们有四种常见的形式: ①一个二维数组,数组的类型是CV32FC1,有n行,3列(n×3) ②类似①,也可以用一个3行n列(3×n)的二维数组 ③④用一个n行1列(n ...
- 对人脑处理视觉的描述(摘《学习OpenCV(中文版)》)
人脑将视觉信号划分入很多个通道,将各种不同的信息输入你的大脑.你的大脑有一个关注系统,会根据任务识别出图像的重要部分,并做重点分析,而其他部分则分析得较少 .在人类视觉流中存在大量的反馈,但是目前我们 ...
- 《学习OpenCV》练习题第五章第二题abc
代码: #include <stdio.h> #include <opencv/highgui.h> #include <opencv/cv.h> #include ...
- 《学习OpenCV》练习题第五章第一题ab
这道题是载入一幅带有有趣纹理的图像并用不同的模板(窗口,核)大小做高斯模糊(高斯平滑),然后比较用5*5大小的窗口平滑图像两次和用11*11大小的窗口平滑图像一次是否接近相同. 先说下我的做法,a部分 ...
- 《学习OpenCV》练习题第四章第八题ab
这道题是利用OpenCV例子程序里自带的人脸检测程序,做点图像的复制操作以及alpha融合. 说明:人脸检测的程序我参照了网上现有的例子程序,没有用我用的OpenCV版本(2.4.5)的facedet ...
- 《学习OpenCV》练习题第四章第三题b
#include <highgui.h> #include <cv.h> #include "opencv_libs.h" /* *<学习OpenCV ...
- 《学习OpenCV》练习题第四章第三题a
#include <highgui.h> #include <cv.h> #include "opencv_libs.h" #pragma comment ...
随机推荐
- C#winform控制textbox输入只能为数字
添加keyPress事件,控制键盘输入只能是自然数: /// <summary> /// 控制键盘输入只能是自然数 /// </summary> /// <param n ...
- jquery 选择器,模糊匹配
按姓名匹配 1,name前缀为aa的所有div的jquery对象 $("div[name^='aa']"); 2,name后缀为aa的所有div的jquery对象 $(" ...
- Windows 下 Nginx + PHP + Xdebug + PHPStorm 调试环境配置
前期条件:安装好 Nginx.PHP.PHPStorm,使得可以正常访问 一.为 PHP 安装 Xdebug 到 Xdebug 的官网(http://xdebug.org/download.php)下 ...
- C++ char*,const char*,string的相互转换
1. string转const char* string s ="abc";constchar* c_s = s.c_str(); 2. const char*转string ...
- CSS :before和:after (转)
前几天的晚上较全面的去看了下css的一些文档和资料,大部分的样式运用都没什么大问题了,只是有些许较陌生,但是也知道他们的存在和实现的是什么样式.今天主要想在这篇学习笔记中写的也不多,主要是针对:bef ...
- Lambda 中如果构建一个查询条件,扔该Where返回我们需要的数据。
有一个需求,比如所 省市县 这三个查询条件 都可能有可能没有,但是我们的查询条件怎么构建呢 首先需要看一下 Lambda中Where这个方法需要什么参数 public static IEnumerab ...
- SQL Server存储机制二
http://blog.csdn.net/ltylove2007/article/details/21084585 http://www.cnblogs.com/anding/p/3254674.ht ...
- MongoDB聚合查询
1.count:查询记录条数 db.user.count() 它也跟find一样可以有条件的 db.user.count({}) 2.distinct:用来找出给定键的所有不同的值 db.user.d ...
- String.format(转)
转自:http://blog.csdn.net/lonely_fireworks/article/details/7962171 方便自己查阅. 常规类型的格式化 String类的format()方法 ...
- zendstudio
如何查看大纲类 1.浏览 ->大纲 2.窗口->显示视图->大纲 在php中 右键 ->源代码->格式,方便我们整理代码