OpenMP多线程编程实验

来源:互联网 发布:知乎live可以回看吗 编辑:程序博客网 时间:2024/05/16 02:31

[实验目的]

1、掌握OpenMP的基本功能、构成方式、句法;

2、掌握OpenMP体系结构、特点与组成;

3、掌握采用OpenMP进行多线程编程的基本使用和调试方法;

[预备知识]

1、熟练掌握C++语言。

2、掌握Visual Studio* .NET*集成开发环境的使用;

3、性能优化及多核技术的基本概念;

4、OpenMP并行程序设计基础。

[实验原理]

1、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 还有更为复杂的情况,但是借助这两个结构和两个子句,便能够解释如何并行 π 程序。

2、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;

3、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 包括在内却是一个很好的想法,以避免日后在程序需要时进行修改。

[实验内容及步骤]

1、Hello world程序

实验例程路径:\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、分别运行程序多次,观测实验结果,并记录,分析每次的结果是否相同,为什么?

2、积分方法求PI值的并行处理化算法

实验例程路径:\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的差异。

3、PI值蒙特卡洛算法的改进与编程

实验例程路径:\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<num_steps;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



0 0