登 录
註 冊
论坛
微波仿真网
注册
登录论坛可查看更多信息
微波仿真论坛
>
程序
>
OpenMP并行程序设计(一)
发帖
回复
3953
阅读
4
回复
[
资料共享
]
OpenMP并行程序设计(一)
离线
febi
UID :2537
注册:
2007-05-11
登录:
2016-05-05
发帖:
123
等级:
退休版主
0楼
发表于: 2007-07-14 18:14:58
OpenMP并行程序设计(一)
fL$U%I3
t"# .I?S0
OpenMP是一个支持共享存储并行设计的库,特别适宜多核CPU上的并行程序设计。今天在双核CPU机器上试了一下OpenMP并行程序设计,发现效率方面超出想象,因此写出来分享给大家。
w1;:B%!H
n$Nb,/o
在VC8.0中项目的属性对话框中,左边框里的“配置属性”下的“C/C++”下的“语言”页里,将OpenMP支持改为“是/(OpenMP)”就可以支持OpenMP了。
X;:qnnO
Lsu_f'p0
先看一个简单的使用了OpenMP程序
#dO8) t
int main(int argc, char* argv[])
J:D{5sE<|
{
s|HpN
#pragma omp parallel for
\iL{q^Im
for (int i = 0; i < 10; i++ )
U)v){g3w)
{
vNWCv
printf("i = %d\n", i);
SfTTB'9
}
XS#Jy n
return 0;
pzr\<U`
}
K<3,=gL9[
这个程序执行后打印出以下结果:
t .\<Q#bN#
i = 0
Cj/J&PDQ
i = 5
!V.2~V[^M
i = 1
Q' b@5o
i = 6
Budo9z_w
i = 2
mM#[XKOC<
i = 7
r<MW8
i = 3
[KcF0%a
i = 8
vD-m FC)
i = 4
meF.`fh
i = 9
t_xO-fT)
9Gh:s6
可见for 循环语句中的内容被并行执行了。(每次运行的打印结果可能会有区别)
[k~}Fe)x
这里要说明一下,#pragma omp parallel for 这条语句是用来指定后面的for循环语句变成并行执行的,当然for循环里的内容必须满足可以并行执行,即每次循环互不相干,后一次循环不依赖于前面的循环。
Xmb001
s2f6;Yc
有关#pragma omp parallel for 这条语句的具体含义及相关OpenMP指令和函数的介绍暂时先放一放,只要知道这条语句会将后面的for循环里的内容变成并行执行就行了。
<Pn]{N
wn1` 9
将for循环里的语句变成并行执行后效率会不会提高呢,我想这是我们最关心的内容了。下面就写一个简单的测试程序来测试一下:
%>io$ o
Ty&Ok*
void test()
ioW&0?,Ym
{
Z:(Zy
int a = 0;
C(Cuk4K
clock_t t1 = clock();
f[ 'uka.U
for (int i = 0; i < 100000000; i++)
{)`tN&\
{
/`kM0=MMa
a = i+1;
OK] _.v}
}
QH5[}zs8
clock_t t2 = clock();
)swu~Wb}U@
printf("Time = %d\n", t2-t1);
&"gQrBa
}
q 3nF\Me0
faIHmU
int main(int argc, char* argv[])
ITssBB9
{
/LK,:6
clock_t t1 = clock();
wH0m^?a!3
#pragma omp parallel for
%|izt/B
for ( int j = 0; j < 2; j++ ){
GbB:K2
test();
zNo>V8B(
}
1CmjEAv%/
clock_t t2 = clock();
)JsmzGC0
printf("Total time = %d\n", t2-t1);
H13kNhV9
(O!Q[WLS
test();
xg.o7-^M
return 0;
'kb|!
}
Bj`ZH~T
zn)Kl%N^
在test()函数中,执行了1亿次循环,主要是用来执行一个长时间的操作。
a%YohfsY?U
在main()函数里,先在一个循环里调用test()函数,只循环2次,我们还是看一下在双核CPU上的运行结果吧:
'eYM;\%('
Time = 297
Vi*HG &DD
Time = 297
dCn'IM1
Total time = 297
#?_8 *?
Time = 297
s(0"r.
hk.vBbhs
可以看到 ..
5Tg[-tl
ozOvpi:k3%
未注册仅能浏览
部分内容
,查看
全部内容及附件
请先
登录
或
注册
共
条评分
磁砖
离线
febi
UID :2537
注册:
2007-05-11
登录:
2016-05-05
发帖:
123
等级:
退休版主
1楼
发表于: 2007-07-14 18:16:19
OpenMP并行程序设计(二)
OpenMP并行程序设计(二)... 1
n7/>+V+
1、fork/join并行执行模式的概念... 1
<DZ$"t
2、OpenMP指令和库函数介绍... 1
+Ze;BKZ3
3、parallel 指令的用法... 3
K05U>151
4、for指令的使用方法... 4
gC+?5_=<
5 sections和section指令的用法
SS6K7
6aKfcvf &
<,*3Av
OpenMP并行程序设计(二)
>%H(0G#X
1、fork/join并行执行模式的概念
=Q*x=}NH
OpenMP是一个编译器指令和库函数的集合,主要是为共享式存储计算机上的并行程序设计使用的。
z/,&w_8,:
前面一篇文章中已经试用了OpenMP的一个Parallel for指令。从上篇文章中我们也可以发现OpenMP并行执行的程序要全部结束后才能执行后面的非并行部分的代码。这就是标准的并行模式fork/join式并行模式,共享存储式并行程序就是使用fork/join式并行的。
<q&4Y+b
标准并行模式执行代码的基本思想是,程序开始时只有一个主线程,程序中的串行部分都由主线程执行,并行的部分是通过派生其他线程来执行,但是如果并行部分没有结束时是不会执行串行部分的,如上一篇文章中的以下代码:
rXR!jZ.hi
int main(int argc, char* argv[])
$wC'qV *
{
5?q6g
clock_t t1 = clock();
K\FLA_J
#pragma omp parallel for
a}Z+"D
for ( int j = 0; j < 2; j++ ){
/2'l=R5#
test();
VWc)AfKe
}
Bo$dIn2_
clock_t t2 = clock();
^yX >^1
printf("Total time = %d\n", t2-t1);
[2PPa9F
HR;I}J 9
test();
_2TL>1KZt
return 0;
24u_}ZQzY
}
DTlId~Dyq
在没有执行完for循环中的代码之前,后面的clock_t t2 = clock();这行代码是不会执行的,如果和调用线程创建函数相比,它相当于先创建线程,并等待线程执行完,所以这种并行模式中在主线程里创建的线程并没有和主线程并行运行。
( 8X^pL
2、OpenMP指令和库函数介绍
V8#NXUg<!
下面来介绍OpenMP的基本指令和常用指令的用法,
8S7#tb@3
在C/C++中,OpenMP指令使用的格式为
U!(es0rX
#pragma omp 指令 [子句[子句]…]
wYr*('uT
前面提到的parallel for就是一条指令,有些书中也将OpenMP的“指令”叫做“编译指导语句”,后面的子句是可选的。例如:
m}7Nu
#pragma omp parallel private(i, j)
]iMqIh"
parallel 就是指令, private是子句
GvZ[3GT
为叙述方便把包含#pragma和OpenMP指令的一行叫做语句,如上面那行叫parallel语句。
Y,Lx6kU
"W~vSbn7
OpenMP的指令有以下一些:
*M/:W =,t
parallel,用在一个代码段之前,表示这段代码将被多个线程并行执行
GVhy }0|
for,用于for循环之前,将循环分配到多个线程中并行执行,必须保证每次循环之间无相关性。
Y]tbwOle
parallel for, parallel 和 for语句的结合,也是用在一个for循环之前,表示for循环的代码将被多个线程并行执行。
z'7XGO'Lo
sections,用在可能会被并行执行的代码段之前
v[r8-0c
parallel sections,parallel和sections两个语句的结合
(vp#?-i
critical,用在一段代码临界区之前
3m| C8:
single,用在一段只被单个线程执行的代码段之前,表示后面的代码段将被单线程执行。
a 7685Y
flush,
n,d)Wwe_`y
barrier,用于并行区内代码的线程同步,所有线程执行到barrier时要停止,直到所有线程都执行到barrier时才继续往下执行。
[+_>g4M~%
atomic,用于指定一块内存区域被制动更新
w+wtr[;wwL
master,用于指定一段代码块由主线程执行
g^^pPVK_
ordered, 用于指定并行区域的循环按顺序执行
CXi[$nF3
threadprivate, 用于指定一个变量是线程私有的。
A"z9t#dv@
OpenMP除上述指令外,还有一些库函数,下面列出几个常用的库函数:
B77`azwF
omp_get_num_procs, 返回运行本线程的多处理机的处理器个数。
4xH/a1&p=
omp_get_num_threads, 返回当前并行区域中的活动线程个数。
!ewT#afyu(
omp_get_thread_num, 返回线程号
*%Fu/
omp_set_num_threads, 设置并行执行代码时的线程个数
Sy']fGvx
omp_init_lock, 初始化一个简单锁
[r!f&R
omp_set_lock, 上锁操作
&%%ix#iF
omp_unset_lock, 解锁操作,要和omp_set_lock函数配对使用。
kD0bdE|
omp_destroy_lock, omp_init_lock函数的配对操作函数,关闭一个锁
jtUqrJFlQ
#; f50j!r
OpenMP的子句有以下一些
f>bL }L
private, 指定每个线程都有它自己的变量私有副本。
Au6Y]
firstprivate,指定每个线程都有它自己的变量私有副本,并且变量要被继承主线程中的初值。
F0~<p[9Nx
lastprivate,主要是用来指定将线程中的私有变量的值在并行处理结束后复制回主线程中的对应变量。
CW2)1%1iz
reduce,用来指定一个或多个变量是私有的,并且在并行处理结束后这些变量要执行指定的运算。
+~~2OU L
nowait,忽略指定中暗含的等待
PQp =bX,
num_threads,指定线程的个数
=^SxZ Bn
schedule,指定如何调度for循环迭代
x[0O*ty-*<
shared,指定一个或多个变量为多个线程间的共享变量
tcO{CI
ordered,用来指定for循环的执行要按顺序执行
(k8}9[3G
copyprivate,用于single指令中的指定变量为多个线程的共享变量
&36SX<vZ
copyin,用来指定一个threadprivate的变量的值要用主线程的值进行初始化。
Z #T
default,用来指定并行处理区域内的变量的使用方式,缺省是shared
B%tWi
3、parallel 指令的用法
T];dFv-GT
parallel 是用来构造一个并行块的,也可以使用其他指令如for、sections等和它配合使用。
<uxLG;R
在C/C++中,parallel的使用方法如下:
gJCZ9{Nl
#pragma omp parallel [for | sections] [子句[子句]…]
BNj_f
{
0zE@?.
//代码
~>HzAo9e
}
xK_oV+
parallel语句后面要跟一个大括号对将要并行执行的代码括起来。
Vn_~ |-Wt
void main(int argc, char *argv[]) {
Rw|'LaW
#pragma omp parallel
bAbR0)
{
S8Y\@C?5
printf(“Hello, World!\n”);
#wo *2(
}
Le:(;:eL>t
}
NSBcYObX
执行以上代码将会打印出以下结果
#y[U2s Se
Hello, World!
{VKFw=$8
Hello, World!
/M+Du,
Hello, World!
* k<@
Hello, World!
{0j_.XZ
可以看得出parallel语句中的代码被执行了四次,说明总共创建了4个线程去执行parallel语句中的代码。
vP=68muD
也可以指定使用多少个线程来执行,需要使用num_threads子句:
G[`1Yw$
void main(int argc, char *argv[]) {
'_^T]fr}
#pragma omp parallel num_threads(8)
tU5uL.( O
{
i")0 3b
printf(“Hello, World!, ThreadId=%d\n”, omp_get_thread_num() );
X^Z!!KTH
}
0|J_'-<
}
P#,;)HF
执行以上代码,将会打印出以下结果:
9Msy=qvYG
Hello, World!, ThreadId = 2
6JDaZh"=K
Hello, World!, ThreadId = 6
1`YU9?
Hello, World!, ThreadId = 4
_CfJ Kp)
Hello, World!, ThreadId = 0
*ziR &Fr!
Hello, World!, ThreadId = 5
cP D_=.&
Hello, World!, ThreadId = 7
&w#!
Hello, World!, ThreadId = 1
I/%v`[
Hello, World!, ThreadId = 3
yu)^s!UY;
从ThreadId的不同可以看出创建了8个线程来执行以上代码。所以parallel指令是用来为一段代码创建多个线程来执行它的。parallel块中的每行代码都被多个线程重复执行。
[:FiA?O]
和传统的创建线程函数比起来,相当于为一个线程入口函数重复调用创建线程函数来创建线程并等待线程执行完。
N0.|Mb"?t
4、for指令的使用方法
\+l*ZNYM3
for指令则是用来将一个for循环分配到多个线程中执行。for指令一般可以和parallel指令合起来形成parallel for指令使用,也可以单独用在parallel语句的并行块中。
?3p7MjvZ
#pragma omp [parallel] for [子句]
15,JD
for循环语句
jj1\oyQ8
}f]Y^>-Ux
先看看单独使用for语句时是什么效果:
tq}45{FH3
int j = 0;
3+15 yEeA
#pragma omp for
m3TR}=n
for ( j = 0; j < 4; j++ ){
pF4Z4?W
printf(“j = %d, ThreadId = %d\n”, j, omp_get_thread_num());
BHf$ %?3z,
}
s2#Ia>5!
执行以上代码后打印出以下结果
I O:*F0
j = 0, ThreadId = 0
<^'{=A>
j = 1, ThreadId = 0
Qr9;CVW
j = 2, ThreadId = 0
IHYLM;@L
j = 3, ThreadId = 0
T!8^R|!a6
从结果可以看出四次循环都在一个线程里执行,可见for指令要和parallel指令结合起来使用才有效果:
](A2,F 9(U
如以下代码就是parallel 和for一起结合成parallel for的形式使用的:
Y}1c>5{bE
int j = 0;
s4~[GO6>
#pragma omp parallel for
Dlq!:dF{&
for ( j = 0; j < 4; j++ ){
5&V=$]t
printf(“j = %d, ThreadId = %d\n”, j, omp_get_thread_num());
nuXL{tg6
}
3f] ;y<Km
执行后会打印出以下结果:
#3QPcoxa
j = 0, ThreadId = 0
v6s,lC5qR
j = 2, ThreadId = 2
L23}{P
j = 1, ThreadId = 1
_ i}W1i
j = 3, ThreadId = 3
;~EQS.Qp
可见循环被分配到四个不同的线程中执行。
1^4:l!0D
PDuc;RG
上面这段代码也可以改写成以下形式:
viG,z4Zf
int j = 0;
1|(Q|
#pragma omp parallel
!:^q_q4
{
+yp:douERi
#pragma omp for
F5Z,Jmi^M
for ( j = 0; j < 4; j++ ){
.VCY|KZ
printf(“j = %d, ThreadId = %d\n”, j, omp_get_thread_num());
6e%@uB}$
}
"FWx;65CR
}
80Dn!9j*
执行以上代码会打印出以下结果:
k~^4
j = 1, ThreadId = 1
M'PZ{6;
j = 3, ThreadId = 3
I I+y
j = 2, ThreadId = 2
U}RBgPX!
j = 0, ThreadId = 0
D &"D[|@
Wn'a'
du66a+@t
在一个parallel 块中也可以有多个for语句,如:
ch8a
int j;
+cfEyiub
#pragma omp parallel
:<t=??4m
{
qcS.=Cj?)
#pragma omp for
s*ZE`/SM3
for ( j = 0; j < 100; j++ ){
kFv*>>X`
…
>ESVHPj]
}
<qwf"Ey
#pragma omp for
ZpV]X(Px(o
for ( j = 0; j < 100; j++ ){
e@Lxduq
…
S^eem_C
}
5e/YEDP
…
}/F$73Xd
}
@PEFl"
Do/R.Mgy*
for 循环语句中,书写是需要按照一定规范来写才可以的,即for循环小括号内的语句要按照一定的规范进行书写,for语句小括号里共有三条语句
@ph!3<(In,
for( i=start; i < end; i++)
Lwr's'ao.
]>t~Bcnm
i=start; 是for循环里的第一条语句,必须写成 “变量=初值” 的方式。如 i=0
}IyF|[
i < end;是for循环里的第二条语句,这个语句里可以写成以下4种形式之一:
HOR8Jwf:
变量 < 边界值
Uj):}xgi'
变量 <= 边界值
6^ wI^`NI
变量 > 边界值
wlT8|
变量 >= 边界值
X.eOw>.
如 i>10 i< 10 i>=10 i>10 等等
%.Ma_4o Z
最后一条语句i++可以有以下9种写法之一
OG/b5U
i++
#i[:oC6m:
++i
QQM:[1;RT
i--
@-'a{hBR
--i
iT>u&0B-
i += inc
R}ki%i5|
i -= inc
zuV%`n
i = i + inc
Io1j%T#ZT
i = inc + i
:V(LBH0
i = i –inc
:&IHdf0+
@- STo/
例如i += 2; i -= 2;i = i + 2;i = i - 2;都是符合规范的写法。
Vxh39eW
5 sections和section指令的用法
^#HaH
section语句是用在sections语句里用来将sections语句里的代码划分成几个不同的段,每段都并行执行。用法如下:
QB*,+u4
#pragma omp [parallel] sections [子句]
<+_XGOt0<
{
Vr1}Zv3K'
#pragma omp section
Y%XF64)6
{
j uA@"SG
代码块
ABN4kM>%
}
RdYmh>c
}
|O{N_-];.
; oyV8P$
先看一下以下的例子代码:
_MBhwNBxZ
void main(int argc, char *argv)
Xp:A;i9
{
h2ROQKL"B
#pragma omp parallel sections {
;6W ]f([
#pragma omp section
Q":_\inF
printf(“section 1 ThreadId = %d\n”, omp_get_thread_num());
#ibwD:{
#pragma omp section
R2,9%!iiX
printf(“section 2 ThreadId = %d\n”, omp_get_thread_num());
]n!V
#pragma omp section
2n:<F9^"
printf(“section 3 ThreadId = %d\n”, omp_get_thread_num());
T/_u;My;
#pragma omp section
ipu!{kJ
printf(“section 4 ThreadId = %d\n”, omp_get_thread_num());
7q?ZieR
}
~_\Ra%
执行后将打印出以下结果:
i]v3CY|3AI
section 1 ThreadId = 0
rH3U;K!
section 2 ThreadId = 2
--K)7
section 4 ThreadId = 3
u>*a@3$f
section 3 ThreadId = 1
hdPGqJE
,/\`Rc^n
从结果中可以发现第4段代码执行比第3段代码早,说明各个section里的代码都是并行执行的,并且各个section被分配到不同的线程执行。
L4>14D\
2~kx3` Q
使用section语句时,需要注意的是这种方式需要保证各个section里的代码执行时间相差不大,否则某个section执行时间比其他section过长就达不到并行执行的效果了。
~#r>@C
r&{8/ 5"
上面的代码也可以改写成以下形式:
A2|Bbqd
void main(int argc, char *argv)
>)kKP8l7
{
Z hfp>D
#pragma omp parallel {
l<v{8:,e #
#pragma omp sections
:_8K8Sa
{
)|~&(+Q?]
#pragma omp section
.z>/A/&+
printf(“section 1 ThreadId = %d\n”, omp_get_thread_num());
EYT^*1,E*
#pragma omp section
AxH;psj
printf(“section 2 ThreadId = %d\n”, omp_get_thread_num());
Kh]es,$D
}
6}^x#9\
#pragma omp sections
(a[BvJf
{
T}&A-V$
hX.cdt_?
#pragma omp section
-9b=-K.y
printf(“section 3 ThreadId = %d\n”, omp_get_thread_num());
\ND]x]5d
#pragma omp section
.g#}2:3
printf(“section 4 ThreadId = %d\n”, omp_get_thread_num());
Jt_=aMY:7
}
*k{Llq
}
$*C }iJsF
执行后将打印出以下结果:
9@*pC@I)
section 1 ThreadId = 0
N]P~`)
section 2 ThreadId = 3
yu;EL>G_AY
section 3 ThreadId = 3
aTvyzr1
section 4 ThreadId = 1
:zHSy&i`
^E70$yB^
这种方式和前面那种方式的区别是,两个sections语句是串行执行的,即第二个sections语句里的代码要等第一个sections语句里的代码执行完后才能执行。
~7:q+\
`<YMkp[
用for语句来分摊是由系统自动进行,只要每次循环间没有时间上的差距,那么分摊是很均匀的,使用section来划分线程是一种手工划分线程的方式,最终并行性的好坏得依赖于程序员。
QVT0.GzR
e>MtDJ5
本篇文章中讲的几个OpenMP指令parallel, for, sections, section实际上都是用来如何创建线程的,这种创建线程的方式比起传统调用创建线程函数创建线程要更方便,并且更高效。
S7+>Mk
当然,创建线程后,线程里的变量是共享的还是其他方式,主线程中定义的变量到了并行块内后还是和传统创建线程那种方式一样的吗?创建的线程是如何调度的?等等诸如此类的问题到下一篇文章中进行讲解。
y\FQt];z)
>z<L 60S
ht-'O"d:
.I`>F/Sjr
Trackback:
http://tb.blog.csdn.net/TrackBack.aspx?PostId=1600189
共
条评分
磁砖
离线
febi
UID :2537
注册:
2007-05-11
登录:
2016-05-05
发帖:
123
等级:
退休版主
2楼
发表于: 2007-10-31 20:20:47
在vs2005中使用时要加上
$<-a>~^Tp
#include <omp.h>
共
条评分
磁砖
离线
xdzxw
UID :1364
注册:
2007-03-22
登录:
2020-02-23
发帖:
80
等级:
仿真二级
3楼
发表于: 2007-10-31 22:31:21
楼主 能不能讲一下Fortran如何使用OpenMP编程?
NP3 e^
我的代码都是用Fortran写的
+/_XSo
谢谢!
共
条评分
一切就像那浮云~
离线
cem-uestc
UID :9061
注册:
2008-03-07
登录:
2019-01-05
发帖:
2575
等级:
荣誉管理员
4楼
发表于: 2008-03-07 23:21:06
一直用VC6。0
oP T)vN?
问一下OpenMp是不是VC2005自带,还是安装
共
条评分
欢迎光临
http://www.mwtee.com/home.php?mod=space&uid=13535
发帖
回复