1、掌握OpenMP的基本功能、构成方式、句法;
2、掌握OpenMP体系结构、特点与组成;
3、掌握采用OpenMP进行多线程编程的基本使用和调试方法;
1、熟练掌握C++语言。
2、掌握Visual Studio* .NET*集成开发环境的使用;
3、性能优化及多核技术的基本概念;
4、OpenMP并行程序设计基础。
OpenMP应用编程接口API是在共享存储体系结构上的一个编程模型,其包含:编译制导(Compiler Directive)、运行库例程(Runtime Library)和环境变量(Environment Variables) 三大部分,并支持增量并行化(Incremental Parallelization),用于实现并行性运算的优化解决方法。
OpenMP 基于 fork-join 的编程模式而设计。OpenMP 程序起初以一条单线程的形式开始运行。如果希望在程序中利用并行,那么就需将额外的线程进行分支,以创建线程组。这些线程在称为“并行区域”的代码区域内并行执行。在并行区域末尾,将等待所有线程全部完成工作,并将其重新结合在一起。那时,最初线程或“主”线程将继续执行,直至遇到下一个并行区域(或程序结束)。
OpenMP 的语言结构根据编译器指示而定义,可为编译器布置任务,以实施理想的并行。在 C 和 C++ 中,这些指示根据制导语句来定义。
OpenMP 制导语句在任何情况下的形式都相同:
#pragma omp construct_name one_or_more_clauses
其中“construct_name”规定了希望执行的并行动作,而“clauses”则对该动作进行修改,或对线程所见的数据环境进行控制。
OpenMP 是一种显式的并行编程语言。一旦线程创建,或者工作已经映射到该线程上,那么必须对希望执行的动作加以详述。因此,即使是 OpenMP 这样简单的 API 也有诸多结构和子句需要学习。所幸的是,仅利用整个 OpenMP 语言的一小部分子集,便可完成大量上述工作。
可利用“parallel”结构在 OpenMP 中创建线程。
#pragma omp parallel
{
A block of statements
}
独自使用时(没有修改任何子句),程序可创建出一系列线程供运行时环境选择(这些线程通常与处理器或内核的数量相等)。每条线程都将根据并行制导语句来执行语句块。该语句块可以是 C 中的任意合法语句组,但是唯一例外的是:不能分支到并行语句块之中或之外。如果线程要全面执行语句组,并且该程序的继发行为还要有意义,那么便不能随意将线程分支到并行区域内的结构之中或之外。这是 OpenMP 的一项公共约束。将这种缺乏某些分支的语句块称为“结构块”。
可以令所有线程执行相同的语句,从而进行大量的并行编程。但是要体验 OpenMP 的全部功能,要做的就不止这些。需要在多条线程之间共享执行语句集的工作。这种方式称为“工作共享”。最常见的工作分享结构是循环结构,在 C 中即为“for”循环
#pragma omp for
但是,这一结构仅对具有规范形式的简单循环起作用
for(i=lower_limit; i
“for”结构执行循环的迭代,并将其打包至那些利用并行结构创建的早期线程组中。循环极限和循环索引 (inc_exp) 的递增表达式需在编译时完全确定,并且这些符号中使用的任何恒量都必须在线程组中保持相同。系统需要得出循环的迭代数量,然后将其映射到能够分发至线程组的集上。如果所有线程均计算相同的索引集,那么上述工作只有通过持续稳定的方式才能实现。
请注意,“for”结构并不能创建线程,只能借助并行结构来做到这点。为了简捷起见,可以将并行结构和“for”结构放在一个制导语句中。
#pragma omp parallel for
此举可创建一个线程组,以便执行紧随其后的循环迭代。
该循环迭代必须是独立的,因此不论迭代的执行顺序如何,或是究竟由哪些线程执行循环的哪些迭代部分,循环结果都将相同。如果一条线程写入变量,另一条线程读取变量,那么将产生循环传递相关性 (loop-carried dependence), 程序也将生成错误的结果。编程人员必须仔细分析循环体,以确保没有任何循环传递相关性的发生。在很多情况下,循环传递相关性来源于保存中间结果(用于指定 的循环迭代)的变量。在此情况下,可以通过声明每条线程都将具有自己的变量值,以除去循环传递相关性。这可通过私有子句来实现。例如,如果循环使用名为“tmp”的变量来保存临时值,那么可将以下子句添加到 OpenMP 结构中,这样它便可用于循环体内部,而不会造成任何循环传递相关性。
private(tmp)
另一种常见情况是循环内出现变量,并用于从每个迭代中累积数值。例如,可以利用循环将某项计算的所有结果进行求和,得出一个数值。这在并行编程中十分常见,通常被称为“规约”。在 OpenMP 中,规约子句表示为
reduction(+:sum)
同私有子句一样,该子句可添加到 OpenMP 结构中,用以提示编译器等待规约。这时,程序便会创建一个临时私有变量,以便为每条线程计算累积操作的部分结果。当该结构运行到最后时,每条线程的值将结合起来产生最终答案。用于该规约的操作在子句中同样进行了详细说明。在这种情况下,此操作为“+”。根据对遭受质疑的数学操作进行特性识别,OpenMP可定义用于规约的私有变量值。例如,对于“+”来说,该值为零。
当然,OpenMP 还有更为复杂的情况,但是借助这两个结构和两个子句,便能够解释如何并行 π 程序。
当前,Visual Studio .net 2005已完全支持OpenMP 2.0标准,通过新的编译器选项 /openmp来支持OpenMP程序的编译与链接。编译器在链接OpenMP程序的时候,会自动地将用户的代码和OpenMP与Windows下实现的库vcomp.Lib链接在一起。OpenMP应用程序在运行的时候会寻找动态链接库vcomp.lib。类似于其他Windows下的应用程序,Visual Studio也提供了程序库和动态链接库的调试版本,分别为vcompd.lib与vcompd.dll,Visual Studio没有提供静态链接库。Visual Studio .net 2005配置方法为:
在工程属性页面中:C/C++ -> Language -> OpenMP Support -> Yes
icc编译器中直接使用/Qopenmp即可支持OpenMP;
为了简单起见,将统一规范所需的步骤,并且只使用默认数量的线程进行工作。在串行π程序中,还有一个需要并行的单循环。除因变量“x”和累积变量“sum”之外,该循环的迭代完全独立。请注意,“x”在此用于计算一个循环迭代内的临时存储。因此,可以通过一个私有子句将该变量定位到每条线程,以便对其进行处理
private(x)
从技术层面上讲,循环控制索引可创建一个循环传递相关性。但是,OpenMP 却认为该循环控制索引需要定位到每条线程之中,以使其自动实现对所有线程的私有化。
累积变量“sum”用于计算总和。这是一个经典规约,因此可以使用规约子句:
reduction(+:sum)
将这些子句添加到“parallel for”结构中,便借助 OpenMP 完成了 π 程序的并行。
#include
static long num_steps = 100000; double step;
void main ()
{ int i; double x, pi, sum = 0.0;
step = 1.0/(double) num_steps;
#pragma omp parallel for private(x) reduction(+:sum)
for (i=0;i<= num_steps; i++){
x = (i+0.5)*step;
sum = sum + 4.0/(1.0+x*x);
}
pi = step * sum;
}
需要注意的是,OpenMP 中同样包括标准的 include file
#include
这规定了编程人员有时需要的 OpenMP 类型和运行时程序库例程。请注意,在此程序中,虽没有利用该语言的这些特性,但是将 OpenMP include file 包括在内却是一个很好的想法,以避免日后在程序需要时进行修改。
实验例程路径:\code\OpenMP\Helloworlds\
1、关闭病毒扫描和监控程序;
2、采用Microsoft DevStudio工具新建工程,并加入实验程序文件:Helloworlds.c;
3、编译,运行程序并记录实验结果;
4、在源程序代码中的找到主程序体:
printf("Hello World\n");
for(i=0;i<6;i++)
printf("Iter:%d\n",i);;
加上OpenMP并行处理结构:
#pragma omp parallel
{
}
运行程序,记录并分析结果;
5、采用/Qopenmp重新编译程序,运行程序,记录并分析结果;
6、设定Openmp线程数,运行程序,记录并分析结果:
C:\>Set OMP_NUM_THREADS=2;
7、使用Intel C++编译器重新编译,记录并比较结果有何不同;
C:\>icl /Qopenmp HelloWorlds.c;
8、分别运行程序多次,观测实验结果,并记录,分析每次的结果是否相同,为什么?
实验例程路径:\code\OpenMP\ pi\
1、关闭病毒扫描和监控程序;
2、采用Microsoft DevStudio工具打开实验程序文件:pi.sln;
3、编译,运行程序并记录实验结果;
4、在源程序代码中的找到主程序体中进行omp方式优化
① 需要并行运算的程序体:
加上#pragma omp parallel
{
}段
② 找到for循环体引入omp并行处理方法
加上#pragma omp for
for(xxx:yyy:zzz)
{
}段
③ 检查所有变量,将需要进行特别声明的变量进行omp处理:
#pragma omp parallel private(varname,vaname)\
reduction(+:varname,varname)\
shared(varname,varname)
{
}段
④ 对于特殊的共享变量,进行加锁处理
pragma omp critical
{
}段
5、使用/Qopenmp参数重新编译程序;
6、设定Openmp线程数:
C:\>Set OMP_NUM_THREADS=?;
7、重新运行程序,观测实验结果,并记录,分析采用OpenMP前后程序性能差异;
8、程序修改位置及修改依据;
9、分析比较程序pi_serial.c和pi.c的差异。
实验例程路径:\code\OpenMP\Montecarlopi\
1、关闭病毒扫描和监控程序;
2、采用Microsoft DevStudio工具打开实验程序文件:Montecarlopi.sln;
3、编译,运行程序并记录实验结果;
4、在源程序代码中的找到主程序体中进行omp方式优化
① 需要并行运算的程序体:
加上#pragma omp parallel
{
}段
② 找到for循环体引入omp并行处理方法
加上#pragma omp for
for(xxx:yyy:zzz)
{
}段
③ 检查所有变量,将需要进行特别声明的变量进行omp处理:
#pragma omp parallel private(varname,vaname)\
reduction(+:varname,varname)\
shared(varname,varname)
{
}段
④ 对于特殊的共享变量,进行加锁处理
pragma omp critical
{
}段
5、使用/Qopenmp参数重新编译程序;
6、设定Openmp线程数:
C:\>Set OMP_NUM_THREADS=?;
7、重新运行程序,观测实验结果,并记录;
8、程序修改位置及修改依据。
9、分析比较程序pimonte_VSL_serial.c和pimonte_VSL.c的差异。
1、OpenMP的主要功能,基本构成体有哪些?
2、试分析如何使用OpenMP实现多线程并行运算,提高系统运算效能,其引入环节应如何选取?
3、实验结果与记录;
4、程序修改思路及分析;
5、试使用OpenMP技术编写一多线程循环程序,说明程序的主要功能,分析OpenMP带来的性能变化。
#include
#include
static long num_steps = 1000000;
#define NUM_THREADS 2
int main()
{
int i;
double x, sum=0, pi=0.0;
double step = 1.0/(double) num_steps;
omp_set_num_threads(NUM_THREADS);
#pragma omp parallel for private(x) reduction(+:sum)
for(i=0;i
x = (i+0.5)*step;
sum += 4.0/(1.0+x*x);
}
printf("pi=%20.14lf\n",sum*step);
return 0;
}
[root@localhost obj-ia32]# gcc -o p3 -fopenmp p3.c
[root@localhost obj-ia32]# ./p3
pi= 3.14159265358990
http://blog.chinaunix.net/uid-13327770-id-2902372.html