首页 > 其他 > 详细

[OpenMP] 并行计算入门

时间:2018-12-06 11:39:26      阅读:184      评论:0      收藏:0      [点我收藏+]

OpenMP并行计算入门

个人理解

OpenMP是一种通过共享内存并行系统的多处理器程序设计的编译处理方案,通过预编译指令告诉编译器哪些代码块需要被并行化,通过拷贝代码块实现并行程序。对于循环的并行化我的理解大概是这样的:

  1. 首先,将循环分成线程数个分组,每个分组执行若干个指令,一个分组代表一个线程
  2. 然后,其中有一个为主线程,其他的均不是主线程,每个分组分别执行自己组内的代码
  3. 当所有组别的代码执行完毕之后,在最后回合,通过主线程将结果带回
  4. 关闭所有线程

配置

我使用的是ubuntu18.04,配置起来比较简单,一般需要安装gcc的。

sudo apt-get install gcc

在编译之前我们需要加上-fopenmp,然后运行就可以执行我们带openmp的程序了。

gcc -fopenmp filename.cpp -o filename

然后我们运行就可以了。

指令学习

for

#pragma omp parallel for[clause[claue..]]

for循环使用parallel for指令很简单,只是要考虑到并行时候的循环依赖问题,下面会讨论到。

reduction

reduction每个线程创建变量的副本,按照operator进行初始化值,然后在线程结束的时候都加到全局变量之上。

int res = 10;
#pragma omp parallel for reduction(+:res)
for(int i=0;i<100000;i++){
    res = res + i;
}

在循环次数比较多的情况下,会发生数据竞争(因为默认在并行体外定义的变量是share的,在里面定义的是private的),所以通过reduction规约,可以避免这种情况发生。

operator对应的初始化值的表:

操作 操作符 初始值
加法 + 0
乘法 * 1
减法 - 0
逻辑与 && 0
逻辑或 || 0
按位与 & 1
按位或 | 0
按位异或 ^ 0
相等 true
不等 false
最大值 max 最小负值
最小值 min 最大正值

sections

sections与for不同的是将代码块分成不同的功能模块,而for做的是将代码块分成不同的数据模块,其特点是“功能并行”,for的并行特点是“数据并行”。例如,通过sections将代码块分成不同的section,每个section可以是功能不同的函数,这就是与for最大的不同。

#pragma omp sections
{
    #pragma omp section
    {
        func1();
    }
    #pragma omp section
    {
        func2();
    }
    //....
}

哪个section执行哪个指令完全取决于实现;如果section个数多于线程数,每个section都将会被执行,但是如果section数少于线程数,有些线程就不会执行任何操作。

single

single指令效果是让一条代码块的语句只由一个线程来处理。

#pragma omp single
{
    func();
}

master

master指令表示只能由主线程来执行,其他线程不能执行该代码块的指令。

#pragma omp master
{
    func();
}

critical

critical指令表明该指令包裹的代码块只能由一个线程来执行,不能被多个线程同时执行。注意,如果多个线程试图执行同一个critical代码块的时候,其他线程会被堵塞,也就是排队等着,直到上一线程完成了代码块的操作,下一个线程才能继续执行这一代码块。

与single不同的是,single只能执行一次,并且是单线程执行,执行完了就不会再执行了,而critical可以多次执行。

int n=0;
#pragma omp parallel shared(n)
{
    #pragma omp critical
    n=n+1;
}

从句

可以通过参数private将变量变成每个线程私有的变量,这样,其他线程对该变量的修改不会影响其他线程的变量。这里空说可能不理解,但是可以通过下面的例子来理解。先来看看什么是循环依赖。

依赖(循环依赖)

#include <iostream>
#include <omp.h>
using namespace std;
int main(){
    int fib[1000]; 
    fib[0] = 1;
    fib[1] = 1;
    #pragma omp parallel for
    for(int i=2;i<20;i++){
        fib[i] = fib[i-2] +fib[i-1];
    }
    for(int i=0;i<20;i++){
        cout<<i<<":"<<fib[i]<<endl;
    }
    return 0;
}
/*
输出结果:
0:1
1:1
2:2
3:3
4:5
5:8
6:13
7:6
8:6
9:12
10:18
11:30
12:0
13:0
14:0
15:0
16:0
17:0
18:0
19:0
*/

可以看到,后面很多数据的结果都是0,还有很多算的是错的(7之后的数据),为什么呢?因为这里有循环依赖。循环依赖指的是不同线程之间有变量之间的依赖,这个例子中,由于线程之间有变量之间的依赖,所以必须要前面的线程算完了,后面的线程再在前面线程算的结果的基础上来算才能算出正确的结果,有些时候呢,我们可以通过更改逻辑来避免循环依赖,从而使循环在并行下也可以得到计算,并且不会算错。给个例子:

double factor = 1;
double sum = 0.0;
int n = 1000;
#pragma omp parallel for reduction(+:sum)
for (int i = 0; i < n; i++)
{
    sum += factor / (2 * i + 1);
    factor = -factor;

}
double pi_approx = 4.0*sum;
cout<<pi_approx<<endl;
//上面的例子会输出错误的值,这是因为不同线程之间有循环依赖
//消除循环依赖后
#pragma omp parallel for reduction(+:sum)
for (int i = 0; i < n; i++)
{
    if(i%2){
        factor = 1;
    }else{
        factor = -1;
    }
    sum += factor / (2 * i + 1);
}
double pi_approx = 4.0*sum;
cout<<pi_approx<<endl;
//这下消除了循环依赖,但结果其实依然不正确,这是因为不同的线程之间的factor其实还是shared的,这样一个线程给shared赋值的时候可能会影响到其他线程对sum求和时读取factor变量的错误,因此我们只需要再改一步,让factor变为private即可。
#pragma omp parallel for reduction(+:sum) private(factor)

private

表明某个变量是私有的,private(sum)

firstprivate

表明某个变量线程私有,但是在进入线程之前先给私有变量赋全局变量的初值

lastprivate

表明某个变量线程私有,但是在线程结束之后将最后一个section的值赋给全局变量,本来也没闹清楚,后来实验了一下,大致清楚了,因为不同section之间计算的结果不一样嘛,就是最后执行完运算的那个section把值赋给全局变量

shared

表明某个变量是线程之间共享的

default

要么是shared,要么是private.

reduction

通过operator规约,避免数据竞争,表明某个变量私有,在线程结束后加到全局变量上去

实验部分

由于我随便跑了一跑高斯模糊算法的手动实现(之前用python)写过,这次由于学了OpenMP这个并行编程的东西,打算跑一跑看看到底能提高多快(我的算法是最原始的算法).

在qt下需要在pro文件中加入以下配置,这里由于我用到了opencv,所以还需要加入这样的配置:

QMAKE_CXXFLAGS+= -fopenmp
LIBS+= -lgomp -lpthread

INCLUDEPATH+=/usr/local/include/usr/local/include/opencv/usr/local/include/opencv2
LIBS+=/usr/local/lib/libopencv_highgui.so/usr/local/lib/libopencv_core.so/usr/local/lib/libopencv_imgproc.so/usr/local/lib/libopencv_imgcodecs.so

下面是parallel和不parallel的结果对比:

#include "mainwindow.h"
#include <QApplication>
#include "opencv2/opencv.hpp"
#include <omp.h>
#include <vector>
#include <time.h>
#include <math.h>
#define PI 3.1415926
using namespace cv;
using namespace std;
Mat getWeight(int r=3){
    int l = r*2+1;
    float sum=0;
    Mat temp(l,l,CV_32FC1);
    //
    #pragma omp parallel for reduction(+:sum)
    for(int i=0;i<temp.rows;i++){
        for(int j=0;j<temp.cols;j++){
            temp.at<float>(i,j) = 0.5/(PI*r*r)*exp(-((i-l/2.0)*(i-l/2.0) + (j-l/2.0)*(j-l/2.0))/2.0/double(r)/double(r));
            sum = sum+ temp.at<float>(i,j);
            //cout<<sum<<endl;
        }
    }
    return temp/sum;
}
float Matrix_sum(Mat src){
    float temp=0;
    for(int i=0;i<src.rows;i++){
        for(int j=0;j<src.cols;j++){
            temp +=src.at<float>(i,j);
        }
    }
    return temp;
}
Mat GaussianBlur_parallel(Mat src,Mat weight,int r=3){
    Mat out(src.size(),CV_32FC1);
    Mat temp;
    int rows = src.rows-r;//shared
    int cols = src.cols-r;//shared
 #pragma omp parallel for
    for(int i=r;i<rows;i++){
        for(int j=r;j<cols;j++){
            Mat temp(src,Range(i-r,i+r+1),Range(j-r,j+r+1));//int to float32
            temp.convertTo(temp,CV_32FC1);
            out.at<float>(i,j) = Matrix_sum(temp.mul(weight));
            //cout<<"thread num is:"<<omp_get_num_threads()<<endl;
        }
    }
    out.convertTo(out,CV_8U);
    return out;
}
Mat GaussianBlur_normal(Mat src,Mat weight,int r=3){
    Mat out(src.size(),CV_32FC1);
    for(int i=r;i<src.rows-r;i++){
        for(int j=r;j<src.cols-r;j++){
            Mat temp(src,Range(i-r,i+r+1),Range(j-r,j+r+1));
            temp.convertTo(temp,CV_32FC1);
            //cout<<Matrix_sum(temp.mul(weight))<<endl;
            //cout<<"("<<i-r<<","<<i+r+1<<")"<<","<<"("<<j-r<<","<<j+r+1<<")"<<"raw:"<<src.size()<<endl;
            out.at<float>(i,j) = Matrix_sum(temp.mul(weight));
        }
    }
    out.convertTo(out,CV_8U);
    return out;
}
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    omp_set_num_threads(8);
    vector<Mat> split_img,out_img;
    Mat src  = imread("/home/xueaoru/projects/srm/car.jpg"),normal_img,parallel_img;
    Mat weight = getWeight();
    split(src,split_img);
    out_img.clear();
    double start_time = (double)getTickCount(),end_time;
    for(auto s:split_img){
        out_img.push_back(GaussianBlur_normal(s,weight));
    }
    end_time = (double)getTickCount();
    cout<<"Normal compute time:"<<(end_time-start_time)*1000/getTickFrequency()<<"ms"<<endl;
    merge(out_img,normal_img);

    imshow("normal GaussianBlur",normal_img);


    out_img.clear();
    start_time = (double)getTickCount();
    for(auto s:split_img){
        out_img.push_back(GaussianBlur_parallel(s,weight));
    }
    end_time = (double)getTickCount();
    cout<<"parallel compute time:"<<(end_time-start_time)*1000/getTickFrequency()<<"ms"<<endl;
    merge(out_img,parallel_img);
    imshow("parallel GaussianBlur",parallel_img);
    imshow("raw",src);

    return a.exec();
}

/*
输出结果如下:
Normal compute time:1609.72ms
parallel compute time:871.309ms
*/

[OpenMP] 并行计算入门

原文:https://www.cnblogs.com/aoru45/p/10075593.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!