3. 并行计算-OpenMP
- 隐式并行程序设计
- 常用传统的语言编程成顺序源编码,把“并行”交给编译器实现自动并行
- 语言容易,编译器难
- 显示并行程序设计
- 在用户程序中出现“并行”的调度语句
- 语言难,编译器容易
3.1 OpenMP简介
OpenMP是共享存储体系结构上的一个并行编程模型。适合于SMP共享内存多处理系统和多核处理器体系结构。
支持的编程语言包括C、C++和Fortran。OpenMP提供了对并行算法的高层抽象描述,特别适合在多核CPU机器上的并行程序设计。编译器根据程序中添加的pragma指令,自动将程序并行处理,使用OpenMP降低了并行编程的难度和复杂度。当编译器不支持OpenMP时,程序会退化成普通(串行)程序。程序中已有的OpenMP指令不会影响程序的正常编译运行。
OpenMP编程模型以线程为基础,由编译制导、运行时库函数、环境变量组成。
3.2 OpenMP程序并行执行模型
OpenMP采用Fork-Join并行执行方式: OpenMP程序开始于一个单独的主线程(Master Thread),然后主线程一直串行执行,直到遇见第一个并行域(Parallel Region),然后开始并行执行并行区域。
- Fork:主线程创建一个并行线程队列,然后,并行域中的代码在不同的线程上并行执行;
- Join:当并行域执行完之后,它们或被同步或被中断,最后只有主线程在执行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z2vQj1zK-1573296306862)(./p1.png)]
3.3 支持条件编译
#include<stdio.h>
int main()
{
#ifdef _OPENMP
printf("Compiled by an OpenMP-compliant implementation.\n");
#endif
return 0;
}
3.4 OpenMP编译器
Compiler/Platform | Compiler | Flag |
---|---|---|
Intel(Linux Opteron/Xeon) | icc/icpc/ifort | -openmp |
PGI(Linux Opteron/Xeon) | pgcc/pgCC/pgf77/pgf90 | -mp |
GNU(Linux Opteron/Xeon/IBM Blue Gene) | gcc/g++/g77/gfortran | -fopenmp |
编译:
icc -openmp -o test test.c
执行:
./test
3.5 OpenMP程序结构
3.5.1 Fortran语言
PROGRAM PROG_NAME
INTEGER VAR1, VAR2 ,VAR3
……….
!$OMP PARALLEL PRIVATE(VAR1, VAR2) SHARED(VAR3)
……….
!$OMP END PARALLEL
……
END
3.5.2 C/C++语言
#include<omp.h>
main()
{
int var1, var2, var3;
……..
#pragma omp parallel private(var1, var2) shared(var 3)
{
………….
}
……………
}
3.6 OpenMP编译制导
OpenMP的并行化是通过使用嵌入到C/C++或Fortran源代码中的编译制导语句来实现。
C/C++编译制导指令以**#pragma omp开始,后边跟具体的功能指令,格式如:!$OMP 指令[子句[,子句] …]**。
Fortran编译制导指令以**!$OMP开始,后边跟具体的功能指令,格式如:#pragma omp 指令[子句[,子句] …]**。
- 在并行域结尾有一个隐式同步(barrier)。
- 子句(clause)用来说明并行域的附加信息。
- 在Fortran语言中,子句间用逗号或空格分隔; C/C++子句间用空格分
常用的功能指令如下:
- parallel:用在一个结构块之前,表示这段代码将被多个线程并行执行;
- for:用于for循环语句之前,表示将循环计算任务分配到多个线程中并行执行,以实现任务分担,必须由编程人员自己保证每次循环之间无数据相关性;
- parallel for:parallel和for指令的结合,也是用在for循环语句之前,表示for循环体的代码将被多个线程并行执行,它同时具有并行域的产生和任务分担两个功能;
- sections:用在可被并行执行的代码段之前,用于实现多个结构块语句的任务分担,可并行执行的代码段各自用section指令标出(注意区分sections和section);
- parallel sections:parallel和sections两个语句的结合,类似于parallel for;
- single:用在并行域内,表示一段只被单个线程执行的代码;
- critical:用在一段代码临界区之前,保证每次只有一个OpenMP线程进入;
- flush:保证各个OpenMP线程的数据影像的一致性;
- barrier:用于并行域内代码的线程同步,线程执行到barrier时要停下等待,直到所有线程都执行到barrier时才继续往下执行;
- atomic:用于指定一个数据操作需要原子性地完成;
- master:用于指定一段代码由主线程执行;
- threadprivate:用于指定一个或多个变量是线程专用。
相应的字句:
- private:指定一个或多个变量在每个线程中都有它自己的私有副
- firstprivate:指定一个或多个变量在每个线程都有它自己的私有副本,并且私有变量要在进入并行域或任务分担域时,继承主线程中的同名变量的值作为初值
- lastprivate:是用来指定将线程中的一个或多个私有变量的值在并行处理结束后复制到主线程中的同名变量中,负责拷贝的线程是for或sections任务分担中的最后一个线程
- reduction:用来指定一个或多个变量是私有的,并且在并行处理结束后这些变量要执行指定的归约运算,并将结果返回给主线程同名变量
- nowait:指出并发线程可以忽略其他制导指令暗含的路障同步
- num_threads:指定并行域内的线程的数目;
- schedule:指定for任务分担中的任务分配调度类型;
- shared:指定一个或多个变量为多个线程间的共享变量
- ordered:用来指定for任务分担域内指定代码段需要按照串行循环次序执行
- copyprivate:配合single指令,将指定线程的专有变量广播到并行域内其他线程的同名变量中;
- copyin:用来指定一个threadprivate类型的变量需要用主线程同名变量进行初始化
- default:用来指定并行域内的变量的使用方式,缺省是shared。
3.7 OpenMP库函数
- omp_get_num_procs :返回运行本线程的多处理机的处理器个数
- omp_get_num_threads:返回当前并行区域中的活动线程个数
- omp_set_num_threads :设置并行执行代码的线程各式个数
- omp_get_max_threads:获得并行域可用的最大线程数目
- omp_get_dynamic:判断是否支持动态改变线程数目
- omp_set_dynamic:启用或关闭线程数目的动态改变
- omp_get_nested:判断系统是否支持并行嵌套
- omp_set_nested:启用或者关闭并行嵌套
- omp_get_thread_num :返回线程号
- omp_init_lock :初始化一个简单锁
- omp_set_lock:上锁操作
- omp_unset_lock :解锁操作
- omp_destroy_lock :和omp_init_lock配对的操作函数,关闭一个锁
- omp_in_parallel:判断当前是否在并行域中
3.8 环境变量
- OMP_SCHEDULE:用于for循环并行化后的调度,它的值就是循环调度的类型;
- OMP_NUM_THREADS :用于设置并行域中的线程数;
- OMP_DYNAMIC:通过设定变量值,来确定是否允许动态设定并行域内的线程数;
- OMP_NESTED:指出是否可以并行嵌套。
3.9 shared和private子句
- 并行域内的变量,可以通过子句说明为公有或私有;
- 在编写多线程程序时,确定哪些数据的公有或私有非常重要:影响程序的性能和正确性
- 通常循环变量、临时变量、写变量一般是私有的;
- 数组变量、仅用于读的变量通常是共享的。默认时为公有。
3.10 reduction子句
- 并行域内的变量,可以通过子句说明为公有或私有;
- 在编写多线程程序时,确定哪些数据的公有或私有非常重要:影响程序的性能和正确性
- 通常循环变量、临时变量、写变量一般是私有的;
- 数组变量、仅用于读的变量通常是共享的。默认时为公有。