OpenMP.docx
《OpenMP.docx》由会员分享,可在线阅读,更多相关《OpenMP.docx(26页珍藏版)》请在冰豆网上搜索。
OpenMP
OpenMP并行程序设计
OpenMP是一个支持共享存储并行设计的库,特别适宜多核CPU上的并行程序设计。
今天在双核CPU机器上试了一下OpenMP并行程序设计,发现效率方面超出想象,因此写出来分享给大家。
在VC8.0中项目的属性对话框中,左边框里的“配置属性”下的“C/C++”下的“语言”页里,将OpenMP支持改为“是/(OpenMP)”就可以支持OpenMP了。
先看一个简单的使用了OpenMP程序
intmain(intargc,char*argv[])
{
#pragmaompparallelfor
for(inti=0;i<10;i++)
printf("i=%d\n",i);
}
return0;
这个程序执行后打印出以下结果:
i=0
i=5
i=1
i=6
i=2
i=7
i=3
i=8
i=4
i=9
可见for循环语句中的内容被并行执行了。
(每次运行的打印结果可能会有区别)
这里要说明一下,#pragmaompparallelfor这条语句是用来指定后面的for循环语句变成并行执行的,当然for循环里的内容必须满足可以并行执行,即每次循环互不相干,后一次循环不依赖于前面的循环。
有关#pragmaompparallelfor这条语句的具体含义及相关OpenMP指令和函数的介绍暂时先放一放,只要知道这条语句会将后面的for循环里的内容变成并行执行就行了。
将for循环里的语句变成并行执行后效率会不会提高呢,我想这是我们最关心的内容了。
下面就写一个简单的测试程序来测试一下:
voidtest()
inta=0;
clock_tt1=clock();
for(inti=0;i<100000000;i++)
a=i+1;
clock_tt2=clock();
printf("Time=%d\n",t2-t1);
for(intj=0;j<2;j++){
test();
printf("Totaltime=%d\n",t2-t1);
在test()函数中,执行了1亿次循环,主要是用来执行一个长时间的操作。
在main()函数里,先在一个循环里调用test()函数,只循环2次,我们还是看一下在双核CPU上的运行结果吧:
Time=297
Totaltime=297
可以看到在for循环里的两次test()函数调用都花费了297ms,但是打印出的总时间却只花费了297ms,后面那个单独执行的test()函数花费的时间也是297ms,可见使用并行计算后效率提高了整整一倍。
Example2:
矩阵拟合法计算Pi
SequetialVersion:
#include
voidmain()
time_tstart,finish;
staticlongnum_steps=1000000000;
doublestep;
inti;
doublex,pi,sum=0.0;
step=1.0/(double)num_steps;
start=clock();
for(i=0;i{x=(i+0.5)*step;sum=sum+4.0/(1.0+x*x);}pi=step*sum;finish=clock();printf("Pi=%16.15f(%dsteps),%ldms\n",pi,num_steps,finish-start);return;}ParallelVersion:#include#include#includevoidmain(){time_tstart,finish;staticlongnum_steps=1000000000;doublestep;inti;doublex,pi,sum=0.0;step=1.0/(double)num_steps;start=clock();#pragmaompparallelforreduction(+:sum)private(x)/*只加了这一句,其他不变*/for(i=0;i{x=(i+0.5)*step;sum=sum+4.0/(1.0+x*x);}pi=step*sum;finish=clock();printf("Pi=%16.15f(%dsteps),%ldms\n",pi,num_steps,finish-start);return;}OpenMP简介2010-02-0808:45P.M.OpenMP是作为共享存储标准而问世的。它是为在多处理机上编写并行程序而设计的一个应用编程接口。它包括一套编译指导语句和一个用来支持它的函数库。目前双核心的CPU当道,AMD的Athlon64x2、Intel的Pentium-D、CoreDuo,以及即将上市的Core2Duo,俨然将成为下一代电脑的主流(尤其是超低价的PentiumD,绝对是现阶段C/P值极高的双核心CPU)。但是双核心有什麼用呢?对於一般单一执行绪(singlethread)的程式,多核心的处理器并没有办法提升它的处理效能;不过对於多执行绪(multithread)的程式,就可以透过不同的核心同时计算,来达到加速的目的了!简单的例子,以单执行绪的程式来说,一件事做一次要十秒的话,要做十次,都丢给同一颗核心做的话,自然就是10秒*10次,也就是100秒了;但是以多执行绪的程式来说,它可以把这一件事,分给两颗核心各自做,每颗核心各做5次,所以所需要的时间就只需要50秒!当然,多执行绪的程式实际上没这麼简单。在工作的切割、结合上,也是要多花时间的,所以在现实中,即使最佳状况,双核心的效能也不会是1+1=2这样的理想化。除此之外,也不是所有工作都是可以切割的!很多工作是有关联性的,这样如果直接切割给不同的处理核心各自去平行运算,出来的结果是肯定有问题的。而且,多执行绪的程式在编写、维护上,也都比单一执行绪的程式复杂上不少。不过,如果电脑本身是多处理器、多核心处理器,或是处理器拥有像IntelHyper-ThreadingTechnology这类的能在同一个时间处理多个执行绪的功能的话,那把各自独立的工作由单一执行绪改成多执行绪,在执行的效率上,大多还是会有增进的!--------------------------------------------------------------------------------多执行绪的程式 写程式的时候该怎麼去写多执行绪的程式呢?一般的方法,就是真的利用thread的控制,去实际在程式中去产生其他的thread来处理。像POSIXThreads这套library,就是用来产生、控制执行绪的函式库。而像MicrosoftVisualStudio2005中,也有提供控制thread的功能。这种方法,大多就是产生多个thread,而再由主要的thread把工作拆开,分给各thread去运算,最後再由主要的thread回收结果、整合。但是,实际上要去控制thread是满麻烦的~在程式的编写上,也会复杂不少;而如果我们只是想要把一些简单的回圈平行化处理,用threadlibrary来控制,实在有点杀鸡用牛刀的感觉。这时候,用OpenMP就简单多了!OpenMP是一种能透过高阶指令,很简单地将程式平行化、多执行绪化的API;在最简单的情形,甚至可以只加一行指令,就可以将回圈内的程式平行化处理了!--------------------------------------------------------------------------------OpenMP的基本使用 要在VisualC++2005中使用OpenMP其实不难,只要将Project的Properties中C/C++里Language的OpenMPSupport开启(参数为/openmp),就可以让VC++2005在编译时支持OpenMP的语法了;而在编写使用OpenMP的程序时,则需要先includeOpenMP的头文件:omp.h。而要将for回圈平行化处理,该怎麼做呢?非常简单,只要在前面加上一行#pragmaompparallelfor就够了!也可以实际用一段简单的程序,来弄清楚它的运作方式。#include#includevoidTest(intn){for(inti=0;i<10000;++i){//donothing,justwastetime}printf("%d,",n);}intmain(intargc,char*argv[]){for(inti=0;i<10;++i)Test(i);system("pause");}上面的程序,在main()是一个很简单的回圈,跑十次,每次都会调用Test()这个函数,并把是回圈的执行次数(i)传进Test()并打印出来。想当然,它的结果会是:0,1,2,3,4,5,6,7,8,9,而如果想利用OpenMP把main()里面的回圈平行化处理呢?只需要修改成下面的样子:#include#include#includevoidTest(intn){for(inti=0;i<10000;++i){//donothing,justwastetime}printf("%d,",n);}intmain(intargc,char*argv[]){#pragmaompparallelforfor(inti=0;i<10;++i)Test(i);system("pause");}够简单吧?重头到尾,只加了两行!而执行后,可以发现结果也变了!0,5,1,6,2,7,3,8,4,9,可以从结果很明显的发现,他没有照着0到9的顺序跑了!而上面的顺序怎麼来的?其实很简单,OpenMP只是把回圈0-9共十个步骤,拆成0-4,5-9两部份,丢给不同的执行绪去跑,所以数字才会出现这样交错性的输出~而要怎麼确定真的有跑多执行绪呢?如果本来有多处理器、多核心处理器或有HyperThread的话,一个单执行绪程序,最多只会把一颗核心的使用量吃完;像比如说在Pentium4HT上跑,单一执行绪的程序,在工作管理员中看到的CPU使用率最多就是50%。而利用OpenMP把回圈进行平行化处理后,就可以在执行回圈时,把两颗核心的CPU都榨光了!也就是CPU使用率是100%。OpenMP是由OpenMPArchitectureReviewBoard牵头提出的,并已被广泛接受的,用于共享内存并行系统的多线程程序设计的一套指导性注释(CompilerDirective)。OpenMP支持的程式語言包括C语言、C++和Fortran;而支持OpenMP的编译器包括SunStudio和IntelCompiler,以及開放源碼的GCC和Open64編譯器。OpenMP提供了对并行算法的高层的抽象描述,程序员通过在原始碼中加入专用的pragma来指明自己的意图,由此编译器可以自动将程序进行并行化,并在必要之处加入同步互斥以及通信。当选择忽略这些pragma,或者编译器不支持OpenMP时,程序又可退化为通常的程序(一般为串行),代码仍然可以正常运作,只是不能利用多线程来加速程序执行。OpenMP提供的这种对于并行描述的高层抽象降低了并行编程的难度和复杂度,这样程序员可以把更多的精力投入到并行算法本身,而非其具体实现细节。对基于数据分集的多线程程序设计,OpenMP是一个很好的选择。同时,使用OpenMP也提供了更强的灵活性,可以较容易的适应不同的并行系统配置。线程粒度和负载平衡等是传统多线程程序设计中的难题,但在OpenMP中,OpenMP库从程序员手中接管了部分这两方面的工作。語法#pragmaomp[clause[[,]clause]...]例子在ompparallel段內的程序代碼由多線程來執行:intmain(intargc,char*argv[]){#pragmaompparallelprintf("Hello,world.\n");return1;}執行結果%gccomp.c(由單線程來執行)%./a.outHello,world.%gcc-fopenmpomp.c(由多線程來執行)%./a.outHello,world.Hello,world.Hello,world.Hello,world.環境變量OpenMP可以使用環境變量OMP_NUM_THREADS以控制執行線程的數量。例子%gcc-fopenmpomp.c%setenvOMP_NUM_THREADS2(由2線程來執行)%./a.outHello,world.Hello,world.爭議作为高层抽象,OpenMP并不适合需要复杂的线程间同步和互斥的场合。OpenMP的另一个缺点是不能在非共享内存系统(如计算机集群)上使用。在这样的系统上,MPI使用较多。OpenMP并行程序设计(二)1、fork/join并行执行模式的概念OpenMP是一个编译器指令和库函数的集合,主要是为共享式存储计算机上的并行程序设计使用的。前面一篇文章中已经试用了OpenMP的一个Parallelfor指令。从上篇文章中我们也可以发现OpenMP并行执行的程序要全部结束后才能执行后面的非并行部分的代码。这就是标准的并行模式fork/join式并行模式,共享存储式并行程序就是使用fork/join式并行的。标准并行模式执行代码的基本思想是,程序开始时只有一个主线程,程序中的串行部分都由主线程执行,并行的部分是通过派生其他线程来执行,但是如果并行部分没有结束时是不会执行串行部分的,如上一篇文章中的以下代码:intmain(intargc,char*argv[]){clock_tt1=clock();#pragmaompparallelforfor(intj=0;j<2;j++){test();}clock_tt2=clock();printf("Totaltime=%d/n",t2-t1);test();return0;}在没有执行完for循环中的代码之前,后面的clock_tt2=clock();这行代码是不会执行的,如果和调用线程创建函数相比,它相当于先创建线程,并等待线程执行完,所以这种并行模式中在主线程里创建的线程并没有和主线程并行运行。2、OpenMP指令和库函数介绍下面来介绍OpenMP的基本指令和常用指令的用法,在C/C++中,OpenMP指令使用的格式为#pragmaomp指令[子句[子句]…]前面提到的parallelfor就是一条指令,有些书中也将OpenMP的“指令”叫做“编译指导语句”,后面的子句是可选的。例如:#pragmaompparallelprivate(i,j)parallel就是指令,private是子句为叙述方便把包含#pragma和OpenMP指令的一行叫做语句,如上面那行叫parallel语句。OpenMP的指令有以下一些:parallel,用在一个代码段之前,表示这段代码将被多个线程并行执行for,用于for循环之前,将循环分配到多个线程中并行执行,必须保证每次循环之间无相关性。parallelfor,parallel和for语句的结合,也是用在一个for循环之前,表示for循环的代码将被多个线程并行执行。sections,用在可能会被并行执行的代码段之前parallelsections,parallel和sections两个语句的结合critical,用在一段代码临界区之前single,用在一段只被单个线程执行的代码段之前,表示后面的代码段将被单线程执行。flush,barrier,用于并行区内代码的线程同步,所有线程执行到barrier时要停止,直到所有线程都执行到barrier时才继续往下执行。atomic,用于指定一块内存区域被制动更新master,用于指定一段代码块由主线程执行ordered,用于指定并行区域的循环按顺序执行threadprivate,用于指定一个变量是线程私有的。OpenMP除上述指令外,还有一些库函数,下面列出几个常用的库函数:omp_get_num_procs,返回运行本线程的多处理机的处理器个数。omp_get_num_threads,返回当前并行区域中的活动线程个数。omp_get_thread_num,返回线程号omp_set_num_threads,设置并行执行代码时的线程个数omp_init_lock,初始化一个简单锁omp_set_lock,上锁操作omp_unset_lock,解锁操作,要和omp_set_lock函数配对使用。omp_destroy_lock,omp_init_lock函数的配对操作函数,关闭一个锁OpenMP的子句有以下一些private,指定每个线程都有它自己的变量私有副本。firstprivate,指定每个线程都有它自己的变量私有副本,并且变量要被继承主线程中的初值。lastprivate,主要是用来指定将线程中的私有变量的值在并行处理结束后复制回主线程中的对应变量。reduce,用来指定一个或多个变量是私有的,并且在并行处理结束后这些变量要执行指定的运算。nowait,忽略指定中暗含的等待num_threads,指定线程的个数schedule,指定如何调度for循环迭代shared,指定一个或多个变量为多个线程间的共享变量ordered,用来指定for循环的执行要按顺序执行copyprivate,用于single指令中的指定变量为多个线程的共享变量copyin,用来指定一个threadprivate的变量的值要用主线程的值进行初始化。default,用来指定并行处理区域内的变量的使用方式,缺省是shared3、parallel指令的用法parallel是用来构造一个并行块的,也可以使用其他指令如for、sections等和它配合使用。在C/C++中,parallel的使用方法如下:#pragmaompparallel[for|sections][子句[子句]…]{//代码}parallel语句后面要跟一个大括号对将要并行执行的代码括起来。voidmain(intargc,char*argv[]){#pragmaompparallel{printf(“Hello,World!/n”);}}执行以上代码将会打印出以下结果Hello,World!Hello,World!Hello,World!Hello,World!可以看得出parallel语句中的代码被执行了四次,说明总共创建了4个线程去执行parallel语句中的代码。-----------------------为什么线程有时候会超过8个呢???中间有重复。xzl也可以指定使用多少个线程来执行,需要使用num_threads子句:voidmain(intargc,char*argv[]){#pragmaompparallelnum_threads(8){printf(“Hello,World!ThreadId=%d/n”,omp_get_thread_num());}}执行以上代码,将会打印出以下结果:Hello,World!ThreadId=2Hello,World!ThreadId=6Hello,World!ThreadId=4Hello,World!ThreadId=0Hello,World!ThreadId=5Hello,World!ThreadId=7Hello,World!ThreadId=1Hello,World!ThreadId=3从ThreadId的不同可以看出创建了8个线程来执行以上代码。所以parallel指令是用来为一段代码创建多个线程来执行它的。parallel块中的每行代码都被多个线程重复执行。和传统的创建线程函数比起来,相当于为一个线程入口函数重复调用创建线程函数来创建线程并等待线程执行完。4、for指令的使用方法for指令则是用来将一个for循环分配到多个线程中执行。for指令一般可以和parallel指令合起来形成parallelfor指令使用,也可以单独用在parallel语句的并行块中。#pragmaomp[parallel]for[子句]for循环语句先看看单独使用for语句时是什么效果:intj=0;#pragmaompforfor(j=0;j<4;j++){printf(“j=%d,ThreadId=%d/n”,j,omp_get_thread
x=(i+0.5)*step;
sum=sum+4.0/(1.0+x*x);
pi=step*sum;
finish=clock();
printf("Pi=%16.15f(%dsteps),%ldms\n",pi,num_steps,finish-start);
return;
ParallelVersion:
#pragmaompparallelforreduction(+:
sum)private(x)/*只加了这一句,其他不变*/
for(i=0;i{x=(i+0.5)*step;sum=sum+4.0/(1.0+x*x);}pi=step*sum;finish=clock();printf("Pi=%16.15f(%dsteps),%ldms\n",pi,num_steps,finish-start);return;}OpenMP简介2010-02-0808:45P.M.OpenMP是作为共享存储标准而问世的。它是为在多处理机上编写并行程序而设计的一个应用编程接口。它包括一套编译指导语句和一个用来支持它的函数库。目前双核心的CPU当道,AMD的Athlon64x2、Intel的Pentium-D、CoreDuo,以及即将上市的Core2Duo,俨然将成为下一代电脑的主流(尤其是超低价的PentiumD,绝对是现阶段C/P值极高的双核心CPU)。但是双核心有什麼用呢?对於一般单一执行绪(singlethread)的程式,多核心的处理器并没有办法提升它的处理效能;不过对於多执行绪(multithread)的程式,就可以透过不同的核心同时计算,来达到加速的目的了!简单的例子,以单执行绪的程式来说,一件事做一次要十秒的话,要做十次,都丢给同一颗核心做的话,自然就是10秒*10次,也就是100秒了;但是以多执行绪的程式来说,它可以把这一件事,分给两颗核心各自做,每颗核心各做5次,所以所需要的时间就只需要50秒!当然,多执行绪的程式实际上没这麼简单。在工作的切割、结合上,也是要多花时间的,所以在现实中,即使最佳状况,双核心的效能也不会是1+1=2这样的理想化。除此之外,也不是所有工作都是可以切割的!很多工作是有关联性的,这样如果直接切割给不同的处理核心各自去平行运算,出来的结果是肯定有问题的。而且,多执行绪的程式在编写、维护上,也都比单一执行绪的程式复杂上不少。不过,如果电脑本身是多处理器、多核心处理器,或是处理器拥有像IntelHyper-ThreadingTechnology这类的能在同一个时间处理多个执行绪的功能的话,那把各自独立的工作由单一执行绪改成多执行绪,在执行的效率上,大多还是会有增进的!--------------------------------------------------------------------------------多执行绪的程式 写程式的时候该怎麼去写多执行绪的程式呢?一般的方法,就是真的利用thread的控制,去实际在程式中去产生其他的thread来处理。像POSIXThreads这套library,就是用来产生、控制执行绪的函式库。而像MicrosoftVisualStudio2005中,也有提供控制thread的功能。这种方法,大多就是产生多个thread,而再由主要的thread把工作拆开,分给各thread去运算,最後再由主要的thread回收结果、整合。但是,实际上要去控制thread是满麻烦的~在程式的编写上,也会复杂不少;而如果我们只是想要把一些简单的回圈平行化处理,用threadlibrary来控制,实在有点杀鸡用牛刀的感觉。这时候,用OpenMP就简单多了!OpenMP是一种能透过高阶指令,很简单地将程式平行化、多执行绪化的API;在最简单的情形,甚至可以只加一行指令,就可以将回圈内的程式平行化处理了!--------------------------------------------------------------------------------OpenMP的基本使用 要在VisualC++2005中使用OpenMP其实不难,只要将Project的Properties中C/C++里Language的OpenMPSupport开启(参数为/openmp),就可以让VC++2005在编译时支持OpenMP的语法了;而在编写使用OpenMP的程序时,则需要先includeOpenMP的头文件:omp.h。而要将for回圈平行化处理,该怎麼做呢?非常简单,只要在前面加上一行#pragmaompparallelfor就够了!也可以实际用一段简单的程序,来弄清楚它的运作方式。#include#includevoidTest(intn){for(inti=0;i<10000;++i){//donothing,justwastetime}printf("%d,",n);}intmain(intargc,char*argv[]){for(inti=0;i<10;++i)Test(i);system("pause");}上面的程序,在main()是一个很简单的回圈,跑十次,每次都会调用Test()这个函数,并把是回圈的执行次数(i)传进Test()并打印出来。想当然,它的结果会是:0,1,2,3,4,5,6,7,8,9,而如果想利用OpenMP把main()里面的回圈平行化处理呢?只需要修改成下面的样子:#include#include#includevoidTest(intn){for(inti=0;i<10000;++i){//donothing,justwastetime}printf("%d,",n);}intmain(intargc,char*argv[]){#pragmaompparallelforfor(inti=0;i<10;++i)Test(i);system("pause");}够简单吧?重头到尾,只加了两行!而执行后,可以发现结果也变了!0,5,1,6,2,7,3,8,4,9,可以从结果很明显的发现,他没有照着0到9的顺序跑了!而上面的顺序怎麼来的?其实很简单,OpenMP只是把回圈0-9共十个步骤,拆成0-4,5-9两部份,丢给不同的执行绪去跑,所以数字才会出现这样交错性的输出~而要怎麼确定真的有跑多执行绪呢?如果本来有多处理器、多核心处理器或有HyperThread的话,一个单执行绪程序,最多只会把一颗核心的使用量吃完;像比如说在Pentium4HT上跑,单一执行绪的程序,在工作管理员中看到的CPU使用率最多就是50%。而利用OpenMP把回圈进行平行化处理后,就可以在执行回圈时,把两颗核心的CPU都榨光了!也就是CPU使用率是100%。OpenMP是由OpenMPArchitectureReviewBoard牵头提出的,并已被广泛接受的,用于共享内存并行系统的多线程程序设计的一套指导性注释(CompilerDirective)。OpenMP支持的程式語言包括C语言、C++和Fortran;而支持OpenMP的编译器包括SunStudio和IntelCompiler,以及開放源碼的GCC和Open64編譯器。OpenMP提供了对并行算法的高层的抽象描述,程序员通过在原始碼中加入专用的pragma来指明自己的意图,由此编译器可以自动将程序进行并行化,并在必要之处加入同步互斥以及通信。当选择忽略这些pragma,或者编译器不支持OpenMP时,程序又可退化为通常的程序(一般为串行),代码仍然可以正常运作,只是不能利用多线程来加速程序执行。OpenMP提供的这种对于并行描述的高层抽象降低了并行编程的难度和复杂度,这样程序员可以把更多的精力投入到并行算法本身,而非其具体实现细节。对基于数据分集的多线程程序设计,OpenMP是一个很好的选择。同时,使用OpenMP也提供了更强的灵活性,可以较容易的适应不同的并行系统配置。线程粒度和负载平衡等是传统多线程程序设计中的难题,但在OpenMP中,OpenMP库从程序员手中接管了部分这两方面的工作。語法#pragmaomp[clause[[,]clause]...]例子在ompparallel段內的程序代碼由多線程來執行:intmain(intargc,char*argv[]){#pragmaompparallelprintf("Hello,world.\n");return1;}執行結果%gccomp.c(由單線程來執行)%./a.outHello,world.%gcc-fopenmpomp.c(由多線程來執行)%./a.outHello,world.Hello,world.Hello,world.Hello,world.環境變量OpenMP可以使用環境變量OMP_NUM_THREADS以控制執行線程的數量。例子%gcc-fopenmpomp.c%setenvOMP_NUM_THREADS2(由2線程來執行)%./a.outHello,world.Hello,world.爭議作为高层抽象,OpenMP并不适合需要复杂的线程间同步和互斥的场合。OpenMP的另一个缺点是不能在非共享内存系统(如计算机集群)上使用。在这样的系统上,MPI使用较多。OpenMP并行程序设计(二)1、fork/join并行执行模式的概念OpenMP是一个编译器指令和库函数的集合,主要是为共享式存储计算机上的并行程序设计使用的。前面一篇文章中已经试用了OpenMP的一个Parallelfor指令。从上篇文章中我们也可以发现OpenMP并行执行的程序要全部结束后才能执行后面的非并行部分的代码。这就是标准的并行模式fork/join式并行模式,共享存储式并行程序就是使用fork/join式并行的。标准并行模式执行代码的基本思想是,程序开始时只有一个主线程,程序中的串行部分都由主线程执行,并行的部分是通过派生其他线程来执行,但是如果并行部分没有结束时是不会执行串行部分的,如上一篇文章中的以下代码:intmain(intargc,char*argv[]){clock_tt1=clock();#pragmaompparallelforfor(intj=0;j<2;j++){test();}clock_tt2=clock();printf("Totaltime=%d/n",t2-t1);test();return0;}在没有执行完for循环中的代码之前,后面的clock_tt2=clock();这行代码是不会执行的,如果和调用线程创建函数相比,它相当于先创建线程,并等待线程执行完,所以这种并行模式中在主线程里创建的线程并没有和主线程并行运行。2、OpenMP指令和库函数介绍下面来介绍OpenMP的基本指令和常用指令的用法,在C/C++中,OpenMP指令使用的格式为#pragmaomp指令[子句[子句]…]前面提到的parallelfor就是一条指令,有些书中也将OpenMP的“指令”叫做“编译指导语句”,后面的子句是可选的。例如:#pragmaompparallelprivate(i,j)parallel就是指令,private是子句为叙述方便把包含#pragma和OpenMP指令的一行叫做语句,如上面那行叫parallel语句。OpenMP的指令有以下一些:parallel,用在一个代码段之前,表示这段代码将被多个线程并行执行for,用于for循环之前,将循环分配到多个线程中并行执行,必须保证每次循环之间无相关性。parallelfor,parallel和for语句的结合,也是用在一个for循环之前,表示for循环的代码将被多个线程并行执行。sections,用在可能会被并行执行的代码段之前parallelsections,parallel和sections两个语句的结合critical,用在一段代码临界区之前single,用在一段只被单个线程执行的代码段之前,表示后面的代码段将被单线程执行。flush,barrier,用于并行区内代码的线程同步,所有线程执行到barrier时要停止,直到所有线程都执行到barrier时才继续往下执行。atomic,用于指定一块内存区域被制动更新master,用于指定一段代码块由主线程执行ordered,用于指定并行区域的循环按顺序执行threadprivate,用于指定一个变量是线程私有的。OpenMP除上述指令外,还有一些库函数,下面列出几个常用的库函数:omp_get_num_procs,返回运行本线程的多处理机的处理器个数。omp_get_num_threads,返回当前并行区域中的活动线程个数。omp_get_thread_num,返回线程号omp_set_num_threads,设置并行执行代码时的线程个数omp_init_lock,初始化一个简单锁omp_set_lock,上锁操作omp_unset_lock,解锁操作,要和omp_set_lock函数配对使用。omp_destroy_lock,omp_init_lock函数的配对操作函数,关闭一个锁OpenMP的子句有以下一些private,指定每个线程都有它自己的变量私有副本。firstprivate,指定每个线程都有它自己的变量私有副本,并且变量要被继承主线程中的初值。lastprivate,主要是用来指定将线程中的私有变量的值在并行处理结束后复制回主线程中的对应变量。reduce,用来指定一个或多个变量是私有的,并且在并行处理结束后这些变量要执行指定的运算。nowait,忽略指定中暗含的等待num_threads,指定线程的个数schedule,指定如何调度for循环迭代shared,指定一个或多个变量为多个线程间的共享变量ordered,用来指定for循环的执行要按顺序执行copyprivate,用于single指令中的指定变量为多个线程的共享变量copyin,用来指定一个threadprivate的变量的值要用主线程的值进行初始化。default,用来指定并行处理区域内的变量的使用方式,缺省是shared3、parallel指令的用法parallel是用来构造一个并行块的,也可以使用其他指令如for、sections等和它配合使用。在C/C++中,parallel的使用方法如下:#pragmaompparallel[for|sections][子句[子句]…]{//代码}parallel语句后面要跟一个大括号对将要并行执行的代码括起来。voidmain(intargc,char*argv[]){#pragmaompparallel{printf(“Hello,World!/n”);}}执行以上代码将会打印出以下结果Hello,World!Hello,World!Hello,World!Hello,World!可以看得出parallel语句中的代码被执行了四次,说明总共创建了4个线程去执行parallel语句中的代码。-----------------------为什么线程有时候会超过8个呢???中间有重复。xzl也可以指定使用多少个线程来执行,需要使用num_threads子句:voidmain(intargc,char*argv[]){#pragmaompparallelnum_threads(8){printf(“Hello,World!ThreadId=%d/n”,omp_get_thread_num());}}执行以上代码,将会打印出以下结果:Hello,World!ThreadId=2Hello,World!ThreadId=6Hello,World!ThreadId=4Hello,World!ThreadId=0Hello,World!ThreadId=5Hello,World!ThreadId=7Hello,World!ThreadId=1Hello,World!ThreadId=3从ThreadId的不同可以看出创建了8个线程来执行以上代码。所以parallel指令是用来为一段代码创建多个线程来执行它的。parallel块中的每行代码都被多个线程重复执行。和传统的创建线程函数比起来,相当于为一个线程入口函数重复调用创建线程函数来创建线程并等待线程执行完。4、for指令的使用方法for指令则是用来将一个for循环分配到多个线程中执行。for指令一般可以和parallel指令合起来形成parallelfor指令使用,也可以单独用在parallel语句的并行块中。#pragmaomp[parallel]for[子句]for循环语句先看看单独使用for语句时是什么效果:intj=0;#pragmaompforfor(j=0;j<4;j++){printf(“j=%d,ThreadId=%d/n”,j,omp_get_thread
OpenMP简介
2010-02-0808:
45P.M.
OpenMP是作为共享存储标准而问世的。
它是为在多处理机上编写并行程序而设计的一个应用编程接口。
它包括一套编译指导语句和一个用来支持它的函数库。
目前双核心的CPU当道,AMD的Athlon64x2、Intel的Pentium-D、CoreDuo,以及即将上市的Core2Duo,俨然将成为下一代电脑的主流(尤其是超低价的PentiumD,绝对是现阶段C/P值极高的双核心CPU)。
但是双核心有什麼用呢?
对於一般单一执行绪(singlethread)的程式,多核心的处理器并没有办法提升它的处理效能;不过对於多执行绪(multithread)的程式,就可以透过不同的核心同时计算,来达到加速的目的了!
简单的例子,以单执行绪的程式来说,一件事做一次要十秒的话,要做十次,都丢给同一颗核心做的话,自然就是10秒*10次,也就是100秒了;但是以多执行绪的程式来说,它可以把这一件事,分给两颗核心各自做,每颗核心各做5次,所以所需要的时间就只需要50秒!
当然,多执行绪的程式实际上没这麼简单。
在工作的切割、结合上,也是要多花时间的,所以在现实中,即使最佳状况,双核心的效能也不会是1+1=2这样的理想化。
除此之外,也不是所有工作都是可以切割的!
很多工作是有关联性的,这样如果直接切割给不同的处理核心各自去平行运算,出来的结果是肯定有问题的。
而且,多执行绪的程式在编写、维护上,也都比单一执行绪的程式复杂上不少。
不过,如果电脑本身是多处理器、多核心处理器,或是处理器拥有像IntelHyper-ThreadingTechnology这类的能在同一个时间处理多个执行绪的功能的话,那把各自独立的工作由单一执行绪改成多执行绪,在执行的效率上,大多还是会有增进的!
--------------------------------------------------------------------------------
多执行绪的程式
写程式的时候该怎麼去写多执行绪的程式呢?
一般的方法,就是真的利用thread的控制,去实际在程式中去产生其他的thread来处理。
像POSIXThreads这套library,就是用来产生、控制执行绪的函式库。
而像MicrosoftVisualStudio2005中,也有提供控制thread的功能。
这种方法,大多就是产生多个thread,而再由主要的thread把工作拆开,分给各thread去运算,最後再由主要的thread回收结果、整合。
但是,实际上要去控制thread是满麻烦的~在程式的编写上,也会复杂不少;而如果我们只是想要把一些简单的回圈平行化处理,用threadlibrary来控制,实在有点杀鸡用牛刀的感觉。
这时候,用OpenMP就简单多了!
OpenMP是一种能透过高阶指令,很简单地将程式平行化、多执行绪化的API;在最简单的情形,甚至可以只加一行指令,就可以将回圈内的程式平行化处理了!
OpenMP的基本使用
要在VisualC++2005中使用OpenMP其实不难,只要将Project的Properties中C/C++里Language的OpenMPSupport开启(参数为/openmp),就可以让VC++2005在编译时支持OpenMP的语法了;而在编写使用OpenMP的程序时,则需要先includeOpenMP的头文件:
omp.h。
而要将for回圈平行化处理,该怎麼做呢?
非常简单,只要在前面加上一行
就够了!
也可以实际用一段简单的程序,来弄清楚它的运作方式。
voidTest(intn){
for(inti=0;i<10000;++i)
//donothing,justwastetime
printf("%d,",n);
for(inti=0;i<10;++i)
Test(i);
system("pause");
上面的程序,在main()是一个很简单的回圈,跑十次,每次都会调用Test()这个函数,并把是回圈的执行次数(i)传进Test()并打印出来。
想当然,它的结果会是:
0,1,2,3,4,5,6,7,8,9,
而如果想利用OpenMP把main()里面的回圈平行化处理呢?
只需要修改成下面的样子:
for(inti=0;i<10000;++i){
intmain(intargc,char*argv[]){
够简单吧?
重头到尾,只加了两行!
而执行后,可以发现结果也变了!
0,5,1,6,2,7,3,8,4,9,
可以从结果很明显的发现,他没有照着0到9的顺序跑了!
而上面的顺序怎麼来的?
其实很简单,OpenMP只是把回圈0-9共十个步骤,拆成0-4,5-9两部份,丢给不同的执行绪去跑,所以数字才会出现这样交错性的输出~
而要怎麼确定真的有跑多执行绪呢?
如果本来有多处理器、多核心处理器或有HyperThread的话,一个单执行绪程序,最多只会把一颗核心的使用量吃完;像比如说在Pentium4HT上跑,单一执行绪的程序,在工作管理员中看到的CPU使用率最多就是50%。
而利用OpenMP把回圈进行平行化处理后,就可以在执行回圈时,把两颗核心的CPU都榨光了!
也就是CPU使用率是100%。
OpenMP是由OpenMPArchitectureReviewBoard牵头提出的,并已被广泛接受的,用于共享内存并行系统的多线程程序设计的一套指导性注释(CompilerDirective)。
OpenMP支持的程式語言包括C语言、C++和Fortran;而支持OpenMP的编译器包括SunStudio和IntelCompiler,以及開放源碼的GCC和Open64編譯器。
OpenMP提供了对并行算法的高层的抽象描述,程序员通过在原始碼中加入专用的pragma来指明自己的意图,由此编译器可以自动将程序进行并行化,并在必要之处加入同步互斥以及通信。
当选择忽略这些pragma,或者编译器不支持OpenMP时,程序又可退化为通常的程序(一般为串行),代码仍然可以正常运作,只是不能利用多线程来加速程序执行。
OpenMP提供的这种对于并行描述的高层抽象降低了并行编程的难度和复杂度,这样程序员可以把更多的精力投入到并行算法本身,而非其具体实现细节。
对基于数据分集的多线程程序设计,OpenMP是一个很好的选择。
同时,使用OpenMP也提供了更强的灵活性,可以较容易的适应不同的并行系统配置。
线程粒度和负载平衡等是传统多线程程序设计中的难题,但在OpenMP中,OpenMP库从程序员手中接管了部分这两方面的工作。
語法
#pragmaomp[clause[[,]clause]...]
例子
在ompparallel段內的程序代碼由多線程來執行:
#pragmaompparallel
printf("Hello,world.\n");
return1;
執行結果
%gccomp.c(由單線程來執行)
%./a.out
Hello,world.
%gcc-fopenmpomp.c(由多線程來執行)
環境變量
OpenMP可以使用環境變量OMP_NUM_THREADS以控制執行線程的數量。
%gcc-fopenmpomp.c
%setenvOMP_NUM_THREADS2(由2線程來執行)
爭議
作为高层抽象,OpenMP并不适合需要复杂的线程间同步和互斥的场合。
OpenMP的另一个缺点是不能在非共享内存系统(如计算机集群)上使用。
在这样的系统上,MPI使用较多。
(二)
1、fork/join并行执行模式的概念
OpenMP是一个编译器指令和库函数的集合,主要是为共享式存储计算机上的并行程序设计使用的。
前面一篇文章中已经试用了OpenMP的一个Parallelfor指令。
从上篇文章中我们也可以发现OpenMP并行执行的程序要全部结束后才能执行后面的非并行部分的代码。
这就是标准的并行模式fork/join式并行模式,共享存储式并行程序就是使用fork/join式并行的。
标准并行模式执行代码的基本思想是,程序开始时只有一个主线程,程序中的串行部分都由主线程执行,并行的部分是通过派生其他线程来执行,但是如果并行部分没有结束时是不会执行串行部分的,如上一篇文章中的以下代码:
printf("Totaltime=%d/n",t2-t1);
在没有执行完for循环中的代码之前,后面的clock_tt2=clock();这行代码是不会执行的,如果和调用线程创建函数相比,它相当于先创建线程,并等待线程执行完,所以这种并行模式中在主线程里创建的线程并没有和主线程并行运行。
2、OpenMP指令和库函数介绍
下面来介绍OpenMP的基本指令和常用指令的用法,
在C/C++中,OpenMP指令使用的格式为
#pragmaomp指令[子句[子句]…]
前面提到的parallelfor就是一条指令,有些书中也将OpenMP的“指令”叫做“编译指导语句”,后面的子句是可选的。
例如:
#pragmaompparallelprivate(i,j)
parallel就是指令,private是子句
为叙述方便把包含#pragma和OpenMP指令的一行叫做语句,如上面那行叫parallel语句。
OpenMP的指令有以下一些:
parallel,用在一个代码段之前,表示这段代码将被多个线程并行执行
for,用于for循环之前,将循环分配到多个线程中并行执行,必须保证每次循环之间无相关性。
parallelfor,parallel和for语句的结合,也是用在一个for循环之前,表示for循环的代码将被多个线程并行执行。
sections,用在可能会被并行执行的代码段之前
parallelsections,parallel和sections两个语句的结合
critical,用在一段代码临界区之前
single,用在一段只被单个线程执行的代码段之前,表示后面的代码段将被单线程执行。
flush,
barrier,用于并行区内代码的线程同步,所有线程执行到barrier时要停止,直到所有线程都执行到barrier时才继续往下执行。
atomic,用于指定一块内存区域被制动更新
master,用于指定一段代码块由主线程执行
ordered,用于指定并行区域的循环按顺序执行
threadprivate,用于指定一个变量是线程私有的。
OpenMP除上述指令外,还有一些库函数,下面列出几个常用的库函数:
omp_get_num_procs,返回运行本线程的多处理机的处理器个数。
omp_get_num_threads,返回当前并行区域中的活动线程个数。
omp_get_thread_num,返回线程号
omp_set_num_threads,设置并行执行代码时的线程个数
omp_init_lock,初始化一个简单锁
omp_set_lock,上锁操作
omp_unset_lock,解锁操作,要和omp_set_lock函数配对使用。
omp_destroy_lock,omp_init_lock函数的配对操作函数,关闭一个锁
OpenMP的子句有以下一些
private,指定每个线程都有它自己的变量私有副本。
firstprivate,指定每个线程都有它自己的变量私有副本,并且变量要被继承主线程中的初值。
lastprivate,主要是用来指定将线程中的私有变量的值在并行处理结束后复制回主线程中的对应变量。
reduce,用来指定一个或多个变量是私有的,并且在并行处理结束后这些变量要执行指定的运算。
nowait,忽略指定中暗含的等待
num_threads,指定线程的个数
schedule,指定如何调度for循环迭代
shared,指定一个或多个变量为多个线程间的共享变量
ordered,用来指定for循环的执行要按顺序执行
copyprivate,用于single指令中的指定变量为多个线程的共享变量
copyin,用来指定一个threadprivate的变量的值要用主线程的值进行初始化。
default,用来指定并行处理区域内的变量的使用方式,缺省是shared
3、parallel指令的用法
parallel是用来构造一个并行块的,也可以使用其他指令如for、sections等和它配合使用。
在C/C++中,parallel的使用方法如下:
#pragmaompparallel[for|sections][子句[子句]…]
//代码
parallel语句后面要跟一个大括号对将要并行执行的代码括起来。
voidmain(intargc,char*argv[]){
printf(“Hello,World!
/n”);
执行以上代码将会打印出以下结果
Hello,World!
可以看得出parallel语句中的代码被执行了四次,说明总共创建了4个线程去执行parallel语句中的代码。
-----------------------为什么线程有时候会超过8个呢?
?
中间有重复。
xzl
也可以指定使用多少个线程来执行,需要使用num_threads子句:
#pragmaompparallelnum_threads(8)
ThreadId=%d/n”,omp_get_thread_num());
执行以上代码,将会打印出以下结果:
ThreadId=2
ThreadId=6
ThreadId=4
ThreadId=0
ThreadId=5
ThreadId=7
ThreadId=1
ThreadId=3
从ThreadId的不同可以看出创建了8个线程来执行以上代码。
所以parallel指令是用来为一段代码创建多个线程来执行它的。
parallel块中的每行代码都被多个线程重复执行。
和传统的创建线程函数比起来,相当于为一个线程入口函数重复调用创建线程函数来创建线程并等待线程执行完。
4、for指令的使用方法
for指令则是用来将一个for循环分配到多个线程中执行。
for指令一般可以和parallel指令合起来形成parallelfor指令使用,也可以单独用在parallel语句的并行块中。
#pragmaomp[parallel]for[子句]
for循环语句
先看看单独使用for语句时是什么效果:
intj=0;
#pragmaompfor
for(j=0;j<4;j++){
printf(“j=%d,ThreadId=%d/n”,j,omp_get_thread
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1