在科学和工程应用中,许多程序都要在循环执行上花大量的时间,如Fortran中的do循环和C语言中的for循环,通过并行中的loop-level可以减少这些循环的运行时间。OpenMP提供了parallel for或parallel do指令来对循环结构进行并行处理,这个指令可以用于大部分的循环结构,它也是OpenMP中使用最多和最频繁的指令。当然,程序员必须清楚哪些循环是可以进行并行的。
OpenMP中用于循环的指令结构为:
C:
#pragma ompparallel for [clause [clause …]]
for循环结构
Fortran:
!$omp parallel do[clause [,] [clause …]]
do循环结构
end do
注:方括号[]表示可选项。
在parallel for或parallel do指令后,紧接着就必须是for循环(对于C语言)或do循环(对于Fortran语言),并形体就是for或do循环中的程序。只是在fortran中还可以添加!$omp end parallel do指令来表示循环并行结束。OpenMP还提供了用于控制并行执行的一些循环选项条件(clause),根据这些条件的类型可以分为范围条件、schedule条件、if条件、ordered条件和copyin条件等。
1、范围条件
范围条件主要有default(shared | none)、shared(list)、private(list)、firstprivate(list)、lastprivate(list)和reduction(operator:list)等。default条件用于设置并形体中涉及到的变量默认是shared共享的还是非共享的,default后的括号中只有值shared或none。shared条件表示其后list列出的那些变量在并形体中是共享的,意思就是说并形体外部和并形体内部都可以对该变量进行读取和修改,它就像全局变量一样。
private条件用于表示其后list列出的那些变量在并形体中是私有的。既然是私有的,那么在并形体外部设置的该变量值无论为多少,在并形体中都将无法使用。所有这些private变量在并形体中都必须初始化设置值,换句话说,这些private的变量在并形体中与在并形体外是完全隔离的、毫无关系的。在for循环中定义的循环变量默认就是private的。下面举一个例子来说明private,如下代码:
// File: PrivateTest.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include<omp.h>
#include<iostream>
using namespace std;
//private测试
int PrivateTest()
{
cout<<"private输出:\n";
inti=0,j=10;
#pragmaomp parallel for private(j)
for(i=0;i<8;i++)
{
cout<<i<<":"<<j<<"\n";
j++;
cout<<i<<":"<<j<<"\n";
}
return0;
}
如果运行该程序,将会出现变量j没有初始化的错误,错误提示如下图所示:
奇怪的是为什么没有提示i?因为i已经初始化为0,而并形体中还没有为j设置初始化值,如果将j=10添加到for循环中,程序就运行正常了。如下代码(红色表示修改的部位):
// File: PrivateTest.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include<omp.h>
#include<iostream>
using namespace std;
//private测试
int PrivateTest()
{
cout<<"private输出:\n";
inti=0,j=10;
#pragmaomp parallel for private(j)
for(i=0;i<8;i++)
{
j=10;
cout<<i<<":"<<j<<"\n";
j++;
cout<<i<<":"<<j<<"\n";
}
return0;
}
firstprivate条件用于表示其后list列出的变量在并形体中私有的,但其在并形体中的初始化值为并行之前设置的值。同样以上面的代码为例:
// File: FirstPrivateTest.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include<omp.h>
#include<iostream>
using namespace std;
//FirstPrivateTest测试
int FirstPrivateTest()
{
cout<<"firstprivate输出:\n";
inti=0,j=10;
#pragmaomp parallel for firstprivate(j)
for(i=0;i<8;i++)
{
cout<<i<<":"<<j<<"\n";
j++;
cout<<i<<":"<<j<<"\n";
}
return0;
}
该代码与之前代码没有什么太大区别,尽管在并形体中没有对j进行初始化,但是它能正常正确的运行,其原因就是将变量j设置成了firstprivate。虽说在并形体中并没有对j进行初始化,但是在并形体中j的初始值就是10。
lastprivate条件与firstprivate的含义类似,只是它设置的变量需要在并形体中初始化,但是其最后的结果可以在并形体外部使用。
// File: LastPrivateTest.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include<omp.h>
#include<iostream>
using namespace std;
//LastPrivateTest测试
int LastPrivateTest()
{
cout<<"lastprivate输出:\n";
inti=0,j=10;
#pragmaomp parallel for lastprivate(j)
for(i=0;i<8;i++)
{
j=5;
cout<<i<<":"<<j<<"\n";
j++;
cout<<i<<":"<<j<<"\n";
}
cout<<"最后j的值为:"<<j<<"\n";
return0;
}
最后输出的j的值为6,注意,j的值不是10,也不是11,也不是5+8=13,而是5+1=6。另外,如果设置变量为lastprivate,它必须在并形体中初始化,否则会出错。
reduction条件具有shared和private的特性,其后结构为(operator: list),其中操作operator用于每次累积时list列出变量的操作。这些操作只针对并形体并行时,它不会影响并形体中该变量的值(包括初始值),但是并形体中该变量值将影响最后该变量在并形体外部的值。该变量将分为两部分,一部分属于shared类型,一部分属于private类型,最终该变量的值就是该变量初始值(在并行之前的值)与并形体中private类型值的operator运算,如果operator为+,则为这些之和。如下面的例子:
// File: ReductionTest.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include<omp.h>
#include<iostream>
using namespace std;
//ReductionTest测试
int ReductionTest()
{
cout<<"reduction输出:\n";
inti=0,j=10;
#pragmaomp parallel for reduction(+:j)
for(i=0;i<8;i++)
{
j=2;
cout<<i<<":"<<j<<"\n";
j++;
cout<<i<<":"<<j<<"\n";
}
cout<<"最后j的值为:"<<j<<"\n";
return0;
}
程序运行结果为:
reduction输出:
0:2
0:3
6:2
6:3
2:2
2:3
3:2
3:3
1:2
1:3
5:2
5:3
4:2
4:3
7:2
7:3
最后j的值为:34
从上面的结果可知,变量j在并形体中是以private形式存在,而最后在并形体之间通过操作(+)来完成各个并形体中j的值与初始值(10)的计算,所以最后j的值为:10+(3)+(3)+(3)+(3)+(3)+(3)+(3)+(3)=34。如果操作符是*,则最终j的值为:10*(3)*(3)*(3)*(3)*(3)*(3)*(3)*(3)=65610。
reduction的操作符主要有:
操作符 | 数据类型 | 初始值 |
+ | 整型,浮点型 | 0 |
- | 整型,浮点型 | 0 |
* | 整型,浮点型 | 1 |
& | 整型 | On |
| | 整型 | 0 |
^ | 整型 | 0 |
&& | 整型 | 1 |
|| | 整型 | 0 |
其中初始值是指在没有为该reduction变量在并形体中初始化时,它默认的值。
如果要列出多个变量,list中用逗号隔开相邻的变量。如果有多种类型的要定义,即有shared又有private等等,相互之间用空格隔开即可。如下格式:
#pragma omp parallel for private(a,b,c) firstprivate(d,e)reduction(+:sum)相关程序源码下载地址:
http://download.csdn.net/detail/xwebsite/3843187