1.OpenMP并行计算
- OpenMP概述
OpenMP是由OpenMP Architecture Review Board牵头提出的,用于共享内存并行系统的多线程程序设计的一套指导性注释,目前已被各界广泛接受,OpenMP并不是一种独立的并行编程语言,而是为了多处理器上编写并行程序而设计的、指导共享内存多线程并行的编译制导指令和应用程序编译接口,支持C、C++、Fortran等编程语言,并且在源代码中以编译器可识别的注释的形式出现。为了支持大规模的工程项目以及保证该方法的兼容性,一些具有国际影响力的软件和硬件厂商共同定义并提出了OpenMP标准,该标准是一种在共享存储体系结构上的可移植编程模型,并且广泛应用于UNIX、Linux、Windows等多种平台上。
OpenMP可以将串行执行的程序转化成并行执行,并且可以加入一些相关的同步或者互斥的操作,在该方法下,编译器将根据代码进行自动并行优化,如果该编译器不支持该类方法,相关代码仍然可以正常运作,但是无法实现多线程的并行计算。OpenMP提供的这种对于并行描述的高层抽象降低了并行编程的难度和复杂度,这样程序员可以把更多的精力投入到并行算法本身,而非其具体实现细节。对基于数据分集的多线程程序设计,OpenMP是一个很好的选择。同时,使用OpenMP也提供了更强的灵活性,可以较容易的适应不同的并行系统配置。线程粒度和负载平衡等是传统多线程程序设计中的难题,但在OpenMP中,OpenMP库从程序员手中接管了这两方面的部分工作,提高程序员们的开发效率。
- OpenMP原理
2.1 OpenMP执行原理
OpenMP主要采用Fork-Join并行执行模型。其中,fork代表着主程序创建新的线程或者唤醒已有进程的操作,join代表着多个线程会合的操作,即在该模型中,程序开始于一个单独的主线程,主线程会一直串行地执行,遇到第一个并行域,通过如下过程完成并行操作:
(1)Fork: 主线程创建一系列并行的线程,由这些线程来完成并行域的代码。
(2)当所有并行线程完成代码的执行后,它们或被同步或被中断,最后只剩下主线程在执行。
使用OpenMP的程序在运行时会维护一个相应的线程池,线程池中的线程用作并行区域中的从线程。当某个线程遇到并行结构并且需要创建包含多个线程的线程组时,该线程将检查线程池并且从中获取空闲线程,将其作为组的从线程。如果线程池中没有足够的空闲线程,那么主线程将获取比所需的要少的从线程。当组完成执行并行区域时,从线程就会返回到该线程池中。
在OpenMP编程的过程中,需要特别注意数据之间是否存在依赖性,因为数据之间的这一性质可能会导致结果出错。此外,OpenMP要求循环过程必须为有限次并且不能使用break,return,exit,goto等退出循环。
1.2.2 OpenMP编译原理
程序的编译过程是将源语言翻译成一个等价的目标程序,对于带有OpenMP制导指令的C语言或者Fortran语言来说,编译后的目标程序是由处理器硬件直接执行的程序,本次报告主要讨论以C语言为基础的OpenMP编译。目前,常见的OpenMP编译方案主要分为从OpenMP/C代码直接编译成目标语言以及先将带有OpenMP指令的C语言转化成标准C语言,然后对C语言进行编译生成目标语言,前者可以有效地降低优化的难度并且提高代码优化的效果,而后者可以有效地简化编译过程降低编译难度,同时也更好地理解OpenMP实现过程。
以GCC编译器为例,GCC编译器具备OpenMP编译的能力,采用的方法是后者方案,即OpenMP的目标语言为标准C语言,然后再对这些标准C语言进行编译最终生成可执行文件,并且在编译过程中需要相应运行库的支持。由于OpenMP指令中使用了一些标准C语言没有的关键字和注释方法,因此在编译过程中,需要对传统的词法分析、语法分析、语义分析等过程进行一定的补充和修改,不仅需要增加关键字的识别,还要增加相关文法和语法生成树相关节点的定义等。
- OpenMP指导语句
OpenMP编译制导指令以#pragma omp开始,并且在该句后面跟具体的功能指令,具体格式为#pragma omp指令[子句[子句]…]。OpenMP编译制导指令种类丰富,具体分为线程创建、负载均衡、数据特性、线程同步、用户层面的函数、环境变量等功能。
- 线程创建
#pragma omp parallel用来创建额外的线程完成并行块的计算。原始的进程一般被称作主线程,并设定它的线程序号为0。#pragma omp parallel是语句中最为简单的、基础的指令,在parallel之前,程序只使用一个线程,而当程序到达parallel的时候,当前环境下的其他线程被启动,当该代码执行后,系统会自动生成一个“隐式路障”,用于等待线程组中的其他线程执行结束。只有当线程组中所有的线程均执行了并行代码块,从线程才会终止,然后主线程继续执行之后的语句。此外,环境中每个线程都有自己的堆栈,在执行上述并行代码块时,在堆栈中线程将创建自己函数里定义的私有变量。
- 负载均衡
在该功能下,OpenMP提供了如下几种方法:
omp for 或omp do:用来将for/do循环的单步计算任务分配给不同的线程。
sections:用来将连续的、但相互独立的代码块分配到不同线程
single:指定一个代码块由一个线程执行,暗含了位于此代码块后的一个同步操作,即只有此代码块执行完后,所有线程才开始下面的计算。
master:与single类似,只是该代码块由主线程执行,而且此代码块后不暗含同步操作。
- 数据特性
OpenMP将数据分成了不同种类用于控制数据的作用域及相关的使用情况,具体分为以下三类:
共享(shared):并行区域内的数据被所有线程共享,能为各线程读写。默认的,并行区域内除了循环计数器外的所有变量都是共享变量。
私有(private):并行区域内的数据为各线程私有,其数据不能为其他线程读写。私有变量只在并行区域存在,它不会被初始化。默认的,循环计数器变量是私有变量。
reduce,用来指定一个或多个变量是私有的,并且在并行处理结束后这些变量要执行指定的运算。
- 线程同步
OpenMP为并行域中的代码提供了多种线程同步的制导指令,以保证必要的执行顺序和特殊要求。具体有以下几种指令:
critical:指定的代码块将串行执行,即各线程只能采用分时的方式执行该代码块。一般用来避免数据竞争(datarace)。
atomic:指定一块内存在下一个指令时将被更新。它仅限定内存更新是属于原子性操作。用此子句后,编译器有可能根据特定的硬件指令来以更高效的方式实现critical子句。
ordered:用于指定并行区域的循环按顺序执行
barrier:用于并行区内代码的线程同步,所有线程执行到barrier时要停止,直到所有线程都执行到barrier时才继续往下执行。
nowait:忽略指定中暗含的同步操作
- 用户层面的函数、环境变量
OpenMP设置了多种函数接口和宏定义数据,用于获取或者设置环境中线程信息,在本报告中只列举部分常用的函数或者变量:
omp_get_num_procs,返回运行本线程的多处理机的处理器个数
omp_get_num_threads,返回当前并行区域中的活动线程个数
omp_get_thread_num,返回线程号
omp_set_num_threads,设置并行执行代码时的线程个数
OMP_NUM_THREADS,用来指定创建线程的个数
OMP_SCHEDULE用来指定任务调度的方式
本文为原创文章,转载请注明出处!
admin:支持一下,感谢分享!,+10,