OpenMP基础----以图像处理中的问题为例
数据竞争可能是由于输出相关引起的,编译器不会进行数据竞争的检测,Intel线程检测器可以检测数据竞争。
用类似于互斥量的机制进行私有化和同步,可以消除数据竞争。
#pragma omp parallel for private(x)
for(i=0;i<80;i++)
{
x=sin(i);
if(x>0.6)x=0.6;
printf("sin(%d)=%f\n",i,x);
}
6.
管理共享数据和私有数据:
private:每个线程都拥有该变量的一个单独的副本,可以私有的访问
1)private:说明列表中的每个变量对于每个线程都应该有一个私有副本。这个私有副本用变量的默认值进行初始化
2)firstprivate:见13数据的Copy-in 和Copy-out
3)lastprivate:见13数据的Copy-in 和Copy-out
4)reduction:
5)threadprivate:指定由每个线程私有的全局变量
有三种方法声明存储单元为私有:
1)使用private,firstprivate,lastprivate,reduction子句
2)使用threadprivate
3)在循环内声明变量,并且不使用static关键字
shared:所有线程都能够访问该单元,并行区域内使用共享变量时,如果存在写操作,必须对共享变量加以保护
default:并行区中所有变量都是共享的,除下列三种情况下:
1)在parallel for循环中,循环索引时私有的。
2)并行区中的局部变量是私有的
3)所有在private,firstprivate,lastprivate,reduction子句中列出的变量是私有的
7.
循环调度与分块
为了提供一种简单的方法以便能够在多个处理器之间调节工作负载,OpenMP给出了四种调度方案:
static,dynamic,runtime,guided.
默认情况下,OpenMP采用静态平均调度策略,但是可以通过调用schedule(kind[,chunksize])子句提供循环调度信息
如:#pragma omp for schedule (kind[,chunk-size]) //chunk-size为块大小
guided根据环境变量里的设置来进行对前三种的调度
在windows环境中,可以在”系统属性|高级|环境变量”对话框中进行设置环境变量。
8.
有效地使用归约:
sum=0;
for(k=0;k<100;k++)
{
sum=sum+func(k);
}
为了完成这种形式的循环计算,其中的操作必须满足算术结合律和交换律,同时sum是共享的,这样循环内部都可以加给这个变量,同时又必须是私有的,以避免在相加时的数据竞争。
reduction子句可以用来有效地合并一个循环中某些关于一个或多个变量的满足结合律的算术归约操作。reduction子句主要用来对一个或多个参数条目指定一个操作符,每个线程将创建参数条目的一个私有拷贝,在区域的结束处,将用私有拷贝的值通过指定的运行符运算,原始的参数条目被运算结果的值更新。
sum=0;
#pragma omp parallel for reduction(+:sum)
for(k=0;k<100;k++)
{
sum=sum+func(k);
}
9.
降低线程开销:当编译器生成的线程被执行时,循环的迭代将被分配给该线程,在并行区的最后,所有的线程都被挂起,等待共同进入下一个并行区、循环或结构化块。
如果并行区域、循环或结构化块是相邻的,那么挂起和恢复线程的开销就是没必要的。
举例如下:
#pragma omp parallel //并行区内
{
#pragma omp for // 任务分配for循环
for(k=0;k<m;k++){
fun1(k);
}
#pragma omp for
for(k=0;k<m;k++){
fun2(k);
}
}
10.任务分配区:
现实中应用程序的所有性能敏感的部分不是都在一个并行区域内执行,所以OpenMP用任务分配区这种结构来处理非循环代码。
任务分配区可以指导OpenMP编译器和运行时库将应用程序中标示出的结构化块分配到用于执行并行区域的一组线程上。
举例如下:
#pragma omp parallel //并行区内
{
#pragma omp for // 任务分配for循环
for(k=0;k<m;k++){
fun1(k);
}
#pragma omp sections private(y,z)
{
#pragme omp section//任务分配section
{y=sectionA(x);}
#pragme omp section
{z=sectionB(x);}
}
}
11.
使用Barrier和Nowait:
栅障(Barrier)是OpenMP用于线程同步的一种方法。线程遇到栅障是必须等待,直到并行区中的所有线程都到达同一点。
注意:在任务分配for循环和任务分配section结构中,我们已经隐含了栅障,在parallel,for,sections,single结构的最后,也会有一个隐式的栅障。
隐式的栅障会使线程等到所有的线程继续完成当前的循环、结构化块或并行区,再继续执行后面的工作。可以使用nowait去掉这个隐式的栅障
去掉隐式栅障,例如:
#pragma omp parallel //并行区内
{
#pragma omp for nowait // 任务分配for循环
for(k=0;k<m;k++){
fun1(k);
}
#pragma omp sections private(y,z)
{
#pragme omp section//任务分配section
{y=sectionA(x);}
#pragme omp section
{z=sectionB(x);}
}
}
因为第一个 任务分配for循环和第二个任务分配section代码块之间不存在数据相关。
加上显示栅障,例如:
#pragma omp parallel shared(x,y,z) num_threads(2)//使用的线程数为2
{
int tid=omp_get_thread_num();
if(tid==0)
y=fun1();//第一个线程得到y
else
z=fun2();//第二个线程得到z
#pragma omp barrier //显示加上栅障,保证y和z在使用前已有值
#pragma omp for
for(k=0;k<100;k++)
x[k]=y+z;
}
12.
单线程和多线程交错执行:
当开发人员为了减少开销而把并行区设置的很大时,有些代码很可能只执行一次,并且由一个线程执行,这样单线程和多线程需要交错执行
举例如下:
#pragma omp parallel //并行区
{
int tid=omp_get_thread_num();//每个线程都调用这个函数,得到线程号
//这个循环被划分到多个线程上进行
#pragma omp for nowait
for(k=0;k<100;k++)
x[k]=fun1(tid);//这个循环的结束处不存在使所有线程进行同步的隐式栅障
#pragma omp master
y=fn_input_only(); //只有主线程会调用这个函数
#pragma omp barrier //添加一个显示的栅障对所有的线程同步,从而确保x[0-99]和y处于就绪状态
//这个循环也被划分到多个线程上进行
#pragma omp for nowait
for(k=0;k<100;k++)
x[k]=y+fn2(x[k]); //这个线程没有栅障,所以不会相互等待
//一旦某个线程执行完上面的代码,不需要等待就可以马上执行下面的代码
#pragma omp single //注意:single后面意味着有隐式barrier
fn_single_print(y);
//所有的线程在执行下面的函数前会进行同步
#pragma omp master
fn_print_array(x);//只有主线程会调用这个函数
}
13.
数据的Copy-in 和Copy-out:
在并行化一个程序的时候,一般都必须考虑如何将私有变量的初值复制进来(Copy-in ),以初始化线程组中各个线程的私有副本。
在并行区的最后,还要将最后一次迭代/结构化块中计算出的私有变量复制出来(Copy-out),复制到主线程中的原始变量中。
firstprivate:使用变量在主线程的值对其在每个线程的对应私有变量进行初始化。一般来说,临时私有变量的初值是未定义的。
lastprivate:可以将最后一次迭代/结构化块中计算出来的私有变量复制出来,复制到主线程对应的变量中,一个变量可以同时用firstprivate和lastprivate来声明。
copyin:将主线程的threadprivate变量的值复制到执行并行区的每个线程的threadprivate变量中。
copyprivate:使用一个私有变量将某一个值从一个成员线程广播到执行并行区的其他线程。该子句可以关联single结构(用于single指令中的指定变量为多个线程的共享变量),在所有的线程都离开该结构中的同步点之前,广播操作就已经完成。
14.
保护共享变量的更新操作:
OpenMP支持critical和atomic编译指导,可以用于保护共享变量的更新,避免数据竞争。包含在某个临界段且由atomic编译指导所标记的代码块可能只由一个线程执行。
例如:#pragma omp critical
{
if(max<new_value) max=new_value;
}
15.
OpenMP库函数(#include <omp.h>):
int omp_get_num_threads(void); //获取当前使用的线程个数
int omp_set_num_threads(int NumThreads);//设置要使用的线程个数
int omp_get_thread_num(void);//返回当前线程号
int omp_get_num_procs(void);//返回可用的处理核个数
下面我们来看一个具体的应用例,从硬盘读入两幅图像,对这两幅图像分别提取特征点,特征点匹配,最后将图像与匹配特征点画出来。理解该例子需要一些图像处理的基本知识,我不在此详细介绍。另外,编译该例需要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 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;
}

两种执行方式做比较,时间为: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,每个里面执行了图像读取、特征点与特征描述子提取。将其简化为伪代码形式即为:

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
OpenMP基础----以图像处理中的问题为例的更多相关文章
- paper 119:[转]图像处理中不适定问题-图像建模与反问题处理
图像处理中不适定问题 作者:肖亮博士 发布时间:09-10-25 图像处理中不适定问题(ill posed problem)或称为反问题(inverse Problem)的研究从20世纪末成为国际上的 ...
- 图像处理中的数学原理具体解释20——主成分变换(PCA)
欢迎关注我的博客专栏"图像处理中的数学原理具体解释" 全文文件夹请见 图像处理中的数学原理具体解释(总纲) http://blog.csdn.net/baimafujinji/ar ...
- 图像处理中任意核卷积(matlab中conv2函数)的快速实现。
卷积其实是图像处理中最基本的操作,我们常见的一些算法比如:均值模糊.高斯模糊.锐化.Sobel.拉普拉斯.prewitt边缘检测等等一些和领域相关的算法,都可以通过卷积算法实现.只不过由于这些算法的卷 ...
- iOS开发OC基础:Xcode中常见英文总结,OC常见英文错误
在开发的过程中难免会遇到很多的错误,可是当看到系统给出的英文时,又不知道是什么意思.所以这篇文章总结了Xcode中常见的一些英文单词及词组,可以帮助初学的人快速了解给出的提示.多练习,就肯定能基本掌握 ...
- 图像处理中的数学原理具体解释21——PCA实例与图像编码
欢迎关注我的博客专栏"图像处理中的数学原理具体解释" 全文文件夹请见 图像处理中的数学原理具体解释(总纲) http://blog.csdn.net/baimafujinji/ar ...
- 图像处理中创建CDib类时无法选择基类类型时怎么办
图像处理中创建CDib类时无法选择基类类型时怎么办? 类的类型选择Generic Class 在下面的篮筐里输入CObject就行了
- 图像处理中的跨度(stride)
原文:图像处理中的跨度(stride) 使用C#的BitmapData 最近要转开发平台,正研究C#.C#好是好,不过处理图片时一个像素一个像素的操作像素不是一般的慢.其实Delphi也一样,但好在D ...
- 完全零基础在Linux中安装 JDK
完全零基础在Linux中安装 JDK 总体思路:先确定没有Java程序了 — 然后创建相应路径文件夹 — 下载JDK — 解压到当前路径 — 自定义文件名称 — 配置环境变量 — 检查是否安装成功 第 ...
- 最简单的可取消多选效果(以从水果篮中挑选水果为例)【jsDEMO】
[功能说明] 最简单的可取消多选效果(以从水果篮中挑选水果为例) [html代码说明] <div class="box" id="box"> < ...
随机推荐
- ●洛谷P2606 [ZJOI2010]排列计数
题链: https://www.luogu.org/problemnew/show/P2606题解: 组合数(DP),Lucas定理 首先应该容易看出,这个排列其实是一个小顶堆. 然后我们可以考虑dp ...
- NOIP2014-5-24模拟赛
Problem 1 护花(flower.cpp/c/pas) [题目描述] 约翰留下他的N(N<=100000)只奶牛上山采木.他离开的时候,她们像往常一样悠闲地在草场里吃草.可是,当他回来的时 ...
- hdu 4777 树状数组+合数分解
Rabbit Kingdom Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) T ...
- BZOJ4079 [Wf2014]Pachinko
完整题面: 设f(i,j)表示路径经过(i,j)这个点的概率,列出方程消元. 但暴力消元的复杂度是$O((nm)^3)$,注意每一次消元只会影响前后m个方程,所以我们可以对于第i行,只存[i-m,i+ ...
- 【完整项目】使用Scrapy模拟HTTP POST,获取完美名字
1. 背景 最近有人委托我给小孩起个名字,说名字最好符合周易五行生克理论,然后给了我个网址,说像是这个网站中的八字测名,输入名字和生辰八字等信息,会给出来这个名字的分数和对未来人生的预测.当父母的自然 ...
- python day3_liaoxuefeng
1.Python的循环有两种,一种是for...in循环,依次把list或tuple中的每个元素迭代出来,看例子: names = ['Michael', 'Bob', 'Tracy'] for na ...
- Timestamp转Calendar
Timestamp scheduleTime = r.getTimestamp("time_recv"); Calendar calendarScheduleTime = Cale ...
- 07_Linux目录文件操作命令4解压缩,文件查找_我的Linux之路
这一节还是一样学习操作目录文件的命令 在这一节,我会讲到解压压缩tar以及zip命令,以及文本查找命令grep tar 打包压缩命令 tar命令可以为linux的文件和目录创建档案 首先要弄清两个概念 ...
- redux 最简例子
方便初学redux的同学学习,这里是最简单的redux例子 1 import React, {Component, PropTypes} from 'react' 2 import ReactDOM ...
- Ubuntu14.04和Windows双系统时无法挂载磁盘解决方法
基本状况:我电脑Ubuntu14.04 和 Windows10 双系统,一个固态磁盘,一个机械磁盘.Ubuntu14.04装固态里面了,固态里没有Windows内容. 问题:Ubuntu14.04系统 ...