• 1. 2006年4月共享存储编程1/108机群应用开发 并行编程原理及 程序设计 Parallel Programming: Fundamentals and Implementation 戴 荣 dair@dawning.com.cn 曙光信息产业有限公司 2006.4
  • 2. 2006年4月共享存储编程2/108参考文献黄铠,徐志伟著,陆鑫达等译. 可扩展并行计算技术,结构与编程. 北京:机械工业出版社, P.33~56,P.227~237, 2000. 陈国良著.并行计算—结构、算法、编程. 北京:高等教育出版社,1999. Barry Wilkinson and Michael Allen. Parallel Programming(Techniques and Applications using Networked Workstations and Parallel Computers). Prentice Hall, 1999. 李晓梅,莫则尧等著. 可扩展并行算法的设计与分析. 北京:国防工业出版社,2000. 张宝琳,谷同祥等著. 数值并行计算原理与方法. 北京:国防工业出版社,1999. 都志辉著. 高性能计算并行编程技术—MPI并行程序设计. 北京:清华大学出版社, 2001.
  • 3. 2006年4月共享存储编程3/108相关网址MPI: http://ww.mpi-forum.org, http://www.mcs.anl.gov/mpi Pthreads: http://www.oreilly.com PVM: http://www.epm.ornl.gov/pvm/ OpemMP: http://www.openmp.org 网上搜索:www.google.com
  • 4. 2006年4月共享存储编程4/108共享存储编程 Programming with Shared Memory
  • 5. 2006年4月共享存储编程5/108共享存储并行机模型体系结构特点: 多台处理机通过互联网络共享一个统一的内存空间,通过单一内存地址来实现处理机间的协调. 内存空间也可由多个存储器模块构成. 每台处理机可以执行相同或不同的指令流,每台处理机可以直接访问到所有数据. 处理机间通信是借助于共享主存来实现的. 可扩展性差,当处理机需要同时访问共享全局变量时,产生内存竞争现象而严重影响效率,比较适合中小规模应用问题的计算和事务处理.
  • 6. 2006年4月共享存储编程6/108共享存储编程标准与特点共享存储器编程标准 Pthreads(线程标准) X3H5(线程标准) OpenMP(最常用的共享存储并行编程方式,是我们讨论的重点.) 共享存储器编程特点 显式多线程库调用.(Pthreads). 编译制导语句,OpenMP等. 语言 C,Fortran77,Fortran90/95,C++…
  • 7. 2006年4月共享存储编程7/108并行编程标准线程库标准(Thread Library) – Win32 API. – POSIX threads线程模型. – X3H5:概念性线程模型 编译制导(Compiler Directives) – OpenMP - portable shared memory parallelism.
  • 8. 2006年4月共享存储编程8/108为什么流行多线程编程?线程:在进程的内部执行的指令序列. 相对于进程,线程开销小: 创建一个线程的时间大约是建立一个新进程的1/30。如在Sun4/75工作上站上,创建一个非绑定线程约为52微秒,而fork()一次的时间为1700微秒。 线程同步时间约是进程同步时间的1/3. 线程与RPC相结合,发挥多处理机的处理能力; 发挥多处理器的处理能力; 开发程序的并发性,改善程序的结构. 容易实现数据共享:由于线程共用内存地址,因此可实现数据共享 例:一高性能Web服务器可为每一打开链接的浏览器分配一个线程,所有线程即可共用同一cache来访问网站的热点话题 统一的标准: 以前各开发商提供互不兼容的线程库,结果导致多线程程序不能很好地移值。自1995年的POSIX线程标准实施之后,极大地促进多线程编程的统一。各系统都支持Pthreads,如Linux、SUN、IBM AIX等。
  • 9. 2006年4月共享存储编程9/108Pthreads线程模型POSIX1003.4a小组研究多线程编程标准. 当标准完成后,大多数支持多线程的系统都支持POSIX接口.很好的改善了多线程编程的可移植性. IEEE Portable Operating System Interface, POSIX, 1003.1-1995标准:POSIX线程模型:pthreads.
  • 10. 2006年4月共享存储编程10/108线程管理(Pthread为例)创建:pthread_create 终止:pthread_exit 汇合:pthread_join 分离:pthread_detach 线程属性初始化:pthread_attr_init 唯一执行:pthread_once
  • 11. 2006年4月共享存储编程11/108同步对象在共享存储多处理器并行机上,线程通过全局变量通信,对于全局变量的操作必须进行同步。 pthread提供两个线程同步原语 : 互斥和条件变量.
  • 12. 2006年4月共享存储编程12/108互斥锁函数 函数 操作 Mutex_init() 初始化一个互斥锁 Mutext_lock() 阻塞式加锁操作 Mutex_trylock() 非阻塞式加锁操作 Mutex_unlock() 解锁 Mutex_destroy() 解除互斥状态
  • 13. 2006年4月共享存储编程13/108条件变量的函数 函数 操作 pthread_cond_init() 初始化条件变量 pthread_cond_wait() 阻塞直至条件为真 pthread_cond_signal() 强制条件为真,解除等待条件的线程的阻塞 pthread_cond_timedwait() 阻塞直到指定条件为真或timeout pthread_cond_broadcast() 解除所有等待条件的线程的阻塞 pthread_cond _destroy() 销毁条件变量
  • 14. 2006年4月共享存储编程14/108Hello World(1)#include #include "stdio.h" void *worker(); main() { pthread_t thread; pthread_create(&thread,NULL,worker,NULL); pthread_join(thread,NULL); } void *worker() { printf("Hello World!\n"); }编译命令 gcc hello.c –lpthread 运行结果 Hello World!
  • 15. 2006年4月共享存储编程15/108pthread_t 线程数据类型 pthread_create(&thread,NULL,worker,NULL); 函数pthread_create()用于创建一新的线程,新线程一旦建立便进入运行状态 参数: 线程指针或句柄 线程属性变量,属性参数:默认为NULL. 属性对象一旦建立可以用于创建多个具有共同属性的线程,线程创建后,可删除属性对象. 线程要执行的函数 传入该执行函数的一个参数,无则NULL.可以是任意类型 线程的终止 线程函数正常终止返回; 自调用pthear_exit()函数; 线程被取消; 线程接收到中止的信号; 当主进程执行exit()后,进程及其全部线程全部终止.
  • 16. 2006年4月共享存储编程16/108pthread_join(pthread_t wait_for,void** status); 等待直到线程结束; 执行该函数的线程发生阻塞,直到由wait_for指定的线程终止; 等与被等的两线程必须是同一进程内部的线程(而且不是分离线程); 返回值 0 成功返回 ESRCH 参数wait_for指定的线程不存在或是一分离线程; EINVAL 线程参数无效; EDEADLK 等待自身结束. 不能有两个线程同时等待同一个线程的结束, 否则其中一个线程正常返回,另外一个返回ESRCH错误.
  • 17. 2006年4月共享存储编程17/108Hello World(2)#include #include "stdio.h" #define numthrds 5 pthread_t *tid; void *worker(); main(){ int i; tid = (pthread_t*) calloc(numthrds,sizeof(pthread_t)); for(i=0;i
  • 18. 2006年4月共享存储编程18/108Hello World(3)#include #include "stdio.h" #define numthrds 5 pthread_t *tid; pthread_mutex_t mutex; int sum=0; void *worker(); main(){ int i; tid = (pthread_t*) calloc(numthrds,sizeof(pthread_t)); pthread_mutex_init(&mutex,NULL); for(i=0;i
  • 19. 2006年4月共享存储编程19/108运行结果 0 was added to the sum in thread 0 10 was added to the sum in thread 1 20 was added to the sum in thread 2 30 was added to the sum in thread 3 40 was added to the sum in thread 4 The sum is 100
  • 20. 2006年4月共享存储编程20/108基于多线程编程的PI求解22
  • 21. 2006年4月共享存储编程21/108#include #include "stdio.h" pthread_mutex_t reduction_mutex; pthread_t *tid; double pi,w; int n; int num_threads; double f(a) double a; { return (4.0/(1.0 + a*a)); } void *PIworker(void* arg) { int i,myid; double sum,mypi,x; /*set individual id to start at 0 */ myid = pthread_self() - tid[0]; /*integrate function*/ sum=0.0; for(i = myid + 1;i <= n; i+=num_threads){ x = w * ((double)i - 0.5); sum += f(x); } mypi = w * sum; /*reduce value*/ pthread_mutex_lock(&reduction_mutex); pi += mypi; pthread_mutex_unlock(&reduction_mutex); return(0); }
  • 22. 2006年4月共享存储编程22/108void main(argc,argv) int argc; char* argv[]; { int i; /*check command line */ if(argc != 3) { printf("Usage: %s Num_intervals Num_threads\n",argv[0]); exit(0); } /*get num intervals and num threads from command line*/ n = atoi(argv[1]); num_threads = atoi(argv[2]); w = 1.0 / (double)n; pi = 0.0; tid = (pthread_t*) calloc (num_threads,sizeof(pthread_t));/*initilize lock*/ if(pthread_mutex_init(&reduction_mutex,NULL)){ fprintf(stderr,"Cannot init lock\n"); exit(1); } /*create the threads*/ for(i = 0; i
  • 23. 2006年4月共享存储编程23/108多线程并行编程特点pthread_create()创建一个新线程比重新启动一个线程花费的时间少: 需要时创建+任务结束立刻杀掉 vs. 维护一大堆的空闲线程并且相互切换. 在加锁的前提下访问共享资源 不支持数据并行,适合于任务级并行,即一个线程单独执行一个任务; 不支持增量并行化,对于一个串行程序,很难用Pthreads进行并行化 Pthreads主要是面向操作系统, 而不是为高性能计算设计的,因此不是并行计算程序设计的主流平台。但是“多线程并发执行”这种思想却被广泛地应用于高性能计算。这就是我们即将要讲的共享存储并行编程的另外一种被并行机制造商和广用并行计算用户广泛接受的平台:OpenMP
  • 24. 2006年4月共享存储编程24/108并行编程标准线程库标准(Thread Library) – Win32 API. – POSIX threads线程模型. – X3H5:概念性线程模型 编译制导(Compiler Directives) – OpenMP - portable shared memory parallelism.
  • 25. 2006年4月共享存储编程25/108www.openmp.orgAn Industry Standard API for Shared Memory Programming An API for Writing Multithreaded Applications 一系列编译制导语句和库函数 使得Fortran, C and C++的多线程编程更加容易
  • 26. 2006年4月共享存储编程26/108与X3H5的关系X3H5是ANSI/X3授权的小组委员会,主要目的是在PCF(the Parallel Computing Forum)工作的基础上,发展并行计算的一个ANSI标准. PCF是一非正式的工业组织,虽在DO循环的并行化方法的标准化方面做一些工作,但在起草拟了一个标准后就草草收场. OpenMP专门针对这类并行化问题,并完成了这项工作,同时得到工业界的广泛支持.
  • 27. 2006年4月共享存储编程27/108ANSI X3H5共享编程标准概念性的编程模型(ANSI标准(1993)) 没有任何商品化的共享存储器系统依附于X3H5,但X3H5的基本概念影响以后共享存储器系统的并行编程.(一些基本概念在OpenMP均出现!) X3H5支持C,Fortran77以及Fortran90语言. X3H5规定的基本的并行结构用于并行性表述: 并行块(分散任务Work Sharing) 并行循环 单进程 parallel { … } end parallelpsections { … } end psectionspdo { … } end pdopsingle { … } end psingle
  • 28. 2006年4月共享存储编程28/108X3H5编程实例program main !程序以顺序模式执行 A !A只由基本线程执行 parallel !转换成并行模式 B !B为每个组员所复制 psections !并行块开始 section C !一个组员执行C section D !另一个组员执行D end psections !等待C和D都结束 psingle 暂时转换成顺序模式 E !E只能被一个组员执行 end psingle !转回并行模式 pdo I=1,6 !并行do循环开始 F(i) !各线程分担循环任务 end pdo no wait !无隐式路障同步 G !更多的复制代码 end parallel !结束并行模式 H !根进程执行H … !更多的并行构造 end线程PQRBBECF(1:2)F(3:4)F(5:6)GGGH隐式barrier隐式barrier隐式barrier无隐式barrier隐式barrierBD各线程以负载平衡方式分担任务 可能为:F(1:1),F(2:2),F(3:6)…
  • 29. 2006年4月共享存储编程29/108X3H5例程执行过程描述程序以顺序方式启动,此时只有一个初始化线程,称为基本线程或主线程.当程序遇到parallel时,通过派生多个子线程转换为并行执行模式(线程数隐式决定).基本线程与它的子线程形成一个组.所有组员并行处理后继并行代码,直至end parallel.然后程序转为顺序模式,只有基本线程继续执行. 子线程遇到内部并行或任务分担构造时,可以继续派生其子线程,从而成为一个新组的基本线程. 线程间同步,通信与交互 隐式路障:parallel, end parallel, end pdo或end psingle处隐式barrier.如果不需,则加no wait; 各处理机通过全局变量通信,通过私有变量封装数据 fork…... barrier…顺序执行顺序执行 并行执行
  • 30. 2006年4月共享存储编程30/108OpenMP: 并行模型Fork-Join 并行模式: 主线程根据需要创建一组子线程进行工作分担. 可对串行程序进行逐步并行化.主线程并行执行区域
  • 31. 2006年4月共享存储编程31/108如何应用OpenMP?OpenMP常用于循环并行化: – 找出最耗时的循环. – 将循环由多线程完成. 在串行程序上加上编译制导语句,完成并行化,因此可先完成串行程序,然后再进行OpenMP并行化. void main() { double Res[1000]; for(int i=0;i<1000;i++) { do_huge_comp(Res[i]); } }void main() { double Res[1000]; #pragma omp parallel for for(int i=0;i<1000;i++) { do_huge_comp(Res[i]); }串行程序并行程序用OpenMP将该循环通过多线程进行任务分割
  • 32. 2006年4月共享存储编程32/108线程间如何交互?OpenMP 是基于共享内存模型. 线程通过共享变量通信. 访问共享变量会导致race condition (竞态状态) race condition:是一种状态,在这种状态下两个实体(例如两个处理过程)对同一资源进行竞争,而系统没有一种机制来测定首先要执行的是哪一个。因此,由于系统不能保证数据的正确处理,其结果是不可预测的。 为了避免线程进入竞态状态: 通过同步对象来保护数据冲突.
  • 33. 2006年4月共享存储编程33/108OpenMP术语大多OpenMP构造是制导语句或pragmas. C和C++的pragmas形式为: #pragma omp construct [clause [clause]…] Fortran中,制导语句形式为以下几种: C$OMP CONSTRUCT [clause [clause]…] !$OMP CONSTRUCT [clause [clause]…](自由书写格式唯一) *$OMP CONSTRUCT [clause [clause]…] 例:以下三种等价(第一行为列数) C23456789 !$OMP PARALLEL DO SHARED(A,B,C) C$OMP PARALLEL DO C$OMP+SHARED(A,B,C) C$OMP PARALLELDOSHARED(A,B,C) 由于OpenMP构造为注释性语句,因此一个OpenMP程序在用不支持OpenMP的编译器编译后,仍为串行程序.
  • 34. 2006年4月共享存储编程34/108Structured blocks(结构化块)结构化块性质: 仅在块顶有一个入口和块底有一个出口; 块功能可通过构造的语法清晰地识别; 块内除Fortran中的STOP语句和c/c++中的exit()语句外,不能有其它分支. 大多OpenMP构造为结构化块.C$OMP PARALLEL 10 … … if(…) goto 10 C$OMP END PARALLEL print *,id C$OMP PARALLEL 10 … 30 … if(…) goto 20 go to 10 C$OMP END PARALLEL if(…) goto 30 20 print *, id一个结构化块一个非结构化块
  • 35. 2006年4月共享存储编程35/108OpenMP结构化块类型OpenMP主要有五类结构化块: 并行区Parallel Regions 任务分割Worksharing 数据环境Data Environment 同步Synchronization 运行时函数/环境变量 在Fortran,C/C++中,OpenMP基本上是一样的.
  • 36. 2006年4月共享存储编程36/108Parallel Regions(并行区)并行区是OpenMP的基本构造,并行区内的代码由各线程同时执行. 当一个线程执行“omp parallel”后,建立一组线程,该线程成为新建立的线程组的主线程.所有线程构成一线程组,各线程以线程ID区分,主线程ID为0. 线程组并行执行并行区内代码. 如:建立一个4线程的并行区: double A[1000]; omp_set_num_threads(4); #pragma omp parallel { int ID = omp_thread_num(); worker(ID,A); }每一线程以不同的线程ID 和相同的参数A执行并行区内代码的一拷贝. ID(=0,1,2,3).
  • 37. 2006年4月共享存储编程37/108并行区的Lecical / dynamic extent 以及Orphaned 制所语句bar.f subroutine whoami external omp_get_thread_num integer iam, omp_get_thread_num iam = omp_get_thread_num() C$OMP CRITICAL print*,’Hello from ‘, iam C$OMP END CRITICAL return endpoo.f C$OMP PARALLEL call whoami C$OMP END PARALLELStatic/lexical extent:在书写上直接包含在并行区内的部分.+Dynamic extent:包括并行区内直接和间接(函数调用)包含的内容,也被称为region.Orphan制导语句:落在子程序中的制导语句,方便于子程序的并行化,免去传统的inline处理
  • 38. 2006年4月共享存储编程38/108并行区代码流程double A[1000]; omp_set_num_threads(4); #pragma omp parallel { int ID = omp_thread_num(); worker(ID,A); }opm_set_num_threads(4)worker(2,A)worker(1,A)worker(3,A)Double A[1000]worker(0,A)所有线程在此处同步(如,隐式barrier同步)每一线程执行相同代码,不同数据.所以,并行区结构也被称为SPMD结构.
  • 39. 2006年4月共享存储编程39/108Hello World(C)#include main() { int myid,numthreads; #pragma omp parallel { myid = omp_get_thread_num(); numthreads = omp_get_num_threads(); printf("Hello World from thread %d of %d!\n",myid,numthreads); } }
  • 40. 2006年4月共享存储编程40/108Hello World(Fortran) PROGRAM HELLO integer myid,numthreads integer omp_get_num_threads,omp_get_thread_num !$omp parallel private(numthreads,myid) numthreads = omp_get_num_threads() myid = omp_get_thread_num() print *, 'Hello World from thread' ,myid,'of',numthreads !$omp end parallel stop end
  • 41. 2006年4月共享存储编程41/108OpenMP并行程序编译支持编译OpenMP的编译器会提供编译器命令选项,以解释OpenMP编译制导语句. IBM AIX xlc编译器,编译器命令选项为-qsmp xlc file.c –qsmp xlf_r file.f -qsmp (xlf_r为IBM AIX4.3为支持多线程编程的编译器) 曙光3000:OS: AIX4.3 -qsmp AIX4.3支持 OpenMP编译选项 Intel C/C++编译器icc, Intel Fortran编译器选项为-openmp icc file.c –openmp ifc file.f –openmp 曙光4000L:OS:Redhat Linux 8.0 PGI C/C++编译器icc, PGI Fortran编译器选项为-mp pgcc file.c –mp pgf77 file.f –mp pgf90 file.f –mp 曙光4000A:OS:SuSE Linux 8.0 / Turbo Linux
  • 42. 2006年4月共享存储编程42/108一些细节(可先不关心)C: #pragma omp parallel [clause[ clause] ...] new-line structured-block Fortran: !$OMP PARALLEL [clause[[,] clause]...] block !$OMP END PARALLEL 子句clause是下列之一: if(expr):根据expr表达式执行结果决定是否并行执行 private(list):变量私有化,默认为全部变量 firstprivate(list):在并行区间之外引用变量首次赋值结果 default(shared | none)(C) DEFAULT(PRIVATE | SHARED | NONE)(Fortran) shared(list):并行区间中的共享变量列表 copyin(list):拷贝主线程的threadprivate公共区数据 reduction(operator: list):归约操作
  • 43. 2006年4月共享存储编程43/108OpenMP结构化块类型OpenMP主要有五类结构化块: 并行区Parallel Regions 任务分割Worksharing DO(Fortran)/for(C)结构:针对循环的并行化结构 Sections:代码段间的并行 Single:强制并行区中某些代码以串行方式执行(如:I/O) 数据环境Data Environment 同步Synchronization 运行时函数/环境变量OpenMP最重要部分
  • 44. 2006年4月共享存储编程44/108循环分割:DO(Fortran)/for(C)结构Fortran !$OMP DO [clause[[,] clause]...] do_loop [!$OMP END DO [NOWAIT]](可选) C/C++ #pragma omp for [clause[ clause] ... ] new-line for-loop C$OMP PARALLEL C$OMP DO DO i=0,n … }#pragma omp parallel #pragma omp for for (i=0;i
  • 45. 2006年4月共享存储编程45/108比较parrallel构造与for构造for(i=0;I
  • 46. 2006年4月共享存储编程46/108并行区与任务分割间的关系并行区和任务分割是OpenMP两类基本的并行性构造; 并行区中的代码对组内的各线程是可见的,也即并行区内的代码由各线程同时执行; 任务分割与并行区不同,它是将一个整体任务按负载平衡的方式分配给各线程来互相配合完成. 并行区是并行的先决条件,任务分割必须要与并行区一起使用才能生效; 并行区构造为!omp parallel; 任务分割构造有:do/for,section,和single三种. 根进程并行区
  • 47. 2006年4月共享存储编程47/108更详细的for语法#pragma omp for [clause[ clause] ... ] new-line for-loop Clause可是下列说明: private(list) firstprivate(list) lastprivate(list) reduction(operator: list) ordered schedule(kind[, chunk_size]) nowait 后面将详细说明
  • 48. 2006年4月共享存储编程48/108更详细的DO语法!$OMP DO [clause[[,] clause]...] do_loop [!$OMP END DO [NOWAIT]] PRIVATE(list) FIRSTPRIVATE(list) LASTPRIVATE(list) REDUCTION({operator|intrinsic_procedure_name}:list) SCHEDULE(type[,chunk]) ORDERED
  • 49. 2006年4月共享存储编程49/108DO/for使用注意事项循环体必须紧接在DO或for之后. For循环必须为一结构化块,且其执行不被break语句中断. 在Fortran中,如果写上END DO制导语句,其必须要紧跟在DO循环的结束之后. 循环变量必须为整形. Schedule, ordered,nowait子句只能出现一次.
  • 50. 2006年4月共享存储编程50/108scheduleSchedule子名决定循环如何在各线程中进行分配: schedule(dynamic[,chunk]) 各线程每次得到chunk_size大小的任务,执行完后继续取得任务,以此反复,直至任务完成(最后一任务可能会小于chunk_size).(任务池) 当chunk_size未被指定时,默认为1. schedule(static[,chunk]) 如果chunk_size被指定, 则各线程按线程号顺序每人每得chunk次的循环任务,如果任务不能一次平分掉,则分配循环进行. 如果chunk_size未被指定,则各线程任务数即为循环数除以所用线程数的结果. schedule(guided[,chunk]) 开始以一大的单位进行分配忆,逐渐减小到chunk指定的值. schedule(runtime) 分配方式与chunk值大小取决于环境变量OMP_SCHEDULE的设置. chunk以循环次数为单位. 示意图见下页.
  • 51. 2006年4月共享存储编程51/108Schedule示意图Work poolWork........Dynamic方式Static方式执行时间Guided方式Work........
  • 52. 2006年4月共享存储编程52/108适用条件静态:适用于大部分情形. 特点: 各线程任务明确,在任务分配时无需同步操作. 运行快的线程需等慢的线程为: 动态:适用于任务数量可变或不确定的情形(如条件收敛循环). 特点: 各线程将要执行的任务不可预见,任务分配需同步操作. Guided:线程异步到达for结构 特点: 首先到达的线程总是分得q=ceiling(n/p)次循环,然后n=max(n-q,p*k),循环分配,直到n=p*k为止. 环境变量:无需重新编译程序,可根据原始输入数据的情况改变任务分配策略.
  • 53. 2006年4月共享存储编程53/108NOWAIT子句(Fortran)C$OMP PARALLEL C$OMP DO do i=1,n a(i)= cos(a(i)) enddo C$OMP END DO C$OMP DO do i=1,n b(i)=a(i)+b(i) enddo C$OMP END DO C$OMP END PARALLELC$OMP PARALLEL C$OMP DO do i=1,n a(i)= cos(a(i)) enddo C$OMP END DO NOWAIT C$OMP DO do i=1,n b(i)=a(i)+b(i) enddo C$OMP END DO C$OMP END PARALLEL隐式 BARRIERNo BARRIER默认循环变量i为私有线程私有类型变量END DO必须紧随enddo,可省略.
  • 54. 2006年4月共享存储编程54/108no wait子句(C)#pragma omp parallel { #pragma omp for for(i=1;i
  • 55. 2006年4月共享存储编程55/108Sections构造(非循环并行)Sections用于程序中大范围的非迭代执行代码段间的并行化.(如前10行和后10行间代码间无依赖关系,可以并行.)缺省时每一个“omp sections”构造结束后有一个barrier同步操作.通过使用 “nowait” 子句禁止隐式barrier同步(在构造语句后直接加即可). 与for结构相类似,OpenMP也提供parallel sections. #pragma omp sections [no wait] { x_calculation(); #pragma omp section y_calculation(); #pragma omp section z_calculation(); }x_calculation(),y_calculation()以及z_calculation()代表三部分之间无依赖关系 的非循环代码段.实质上它们各代表很多行代码.
  • 56. 2006年4月共享存储编程56/108SingleSingle:强制并行区中某些代码以串行方式执行 #pragma omp single [clause[ clause] ...] new-line structured-block Clause is one of the following: private(list) firstprivate(list) nowait
  • 57. 2006年4月共享存储编程57/108Work sharing语句汇总Fortran DO SECTIONS SINGLE WORKSHAREC for sections single
  • 58. 2006年4月共享存储编程58/108Binding 并行结构的联合使用#pragma omp parallel for for (I=0;I
  • 59. 2006年4月共享存储编程59/108Fortran!$OMP PARALLEL SECTIONS [clause[[,] clause]...] [!$OMP SECTION ] block [!$OMP SECTION block] . . . !$OMP END PARALLEL SECTIONS!$OMP PARALLEL DO [clause[[,] clause]...] do_loop [!$OMP END PARALLEL DO]!$OMP PARALLEL WORKSHARE [clause[[,] clause]...] block !$OMP END PARALLEL WORKSHARE
  • 60. 2006年4月共享存储编程60/108C#pragma omp parallel for [clause[ clause] ...] new-line for-loop#pragma omp parallel sections [clause[ clause] ...] new-line { [#pragma omp section new-line] structured-block [#pragma omp section new-line structured-block . .] }
  • 61. 2006年4月共享存储编程61/108OpenMP结构化块类型OpenMP主要有五类结构化块: 并行区Parallel Regions 任务分割Worksharing 数据环境Data Environment 同步Synchronization 运行时函数/环境变量
  • 62. 2006年4月共享存储编程62/108默认数据类型共享变量编程模型: – 大部分变量默认为共享类型 各线程共享全局变量 – Fortran: COMMON 块, SAVE 变量, MODULE 变量 – C: File scope variables, static 但并不是所有变量全部共享... – 在并行区内调用的子程序中的栈变量是私有 – 在语句块中的Auto变量为私有变量.
  • 63. 2006年4月共享存储编程63/108举例说明变量类型subroutine work common /input/ A(10) real temp(10) integer count save count …………program sort common /input/ A(10) integer index(10) call input C$OMP PARALLEL call work(index) C$OMP END PARALLEL print*, index(1)变量A,index和cound为所有线程共享. temp为线程私有变量,各线程间到不可见.
  • 64. 2006年4月共享存储编程64/108变量类型改变变量类型编译制导: threadprivate:将某些全局变量或数据区变为线程私有. 通过下列类型属性子句来改变变量类型: – shared(并行区结构) – private – firstprivate – lastprivate:循环体内的私有变量可以转变为全局变量, 在循环结束后访问; 默认数据类型状态可以改变: – default (private | shared | none)
  • 65. 2006年4月共享存储编程65/108私有类型变量变量私有化是OpenMP采用的重要的编译技术. 变量私有化; 私有变量初始化: Copyin Firstprivate 私有变量返回: Reduction Lastprivate OpenMP私有类型变量是指并行区内的只能被线程组内的某一线程访问的变量. OpenMP的私有变量包括: 并行区内定义的变量; 由threadprivate 制导语句指明的变量; 由private, firstprivate,lastprivate, or reduction 子句指定的变量; 循环控制变量.
  • 66. 2006年4月共享存储编程66/108Threadprivate: 线程的全局私有变量 parameter (N=1000) real A(N,N) C$OMP THREADPRIVATE(/buf/) common/buf/lft(N),rht(N) C$OMP PARALLEL call init call scale call order C$OMP END PARALLEL subroutine scale parameter (N=1000) C$OMP THREADPRIVATE(/buf/) common/buf/lft(N),rht(N) do i=1, N lft(i)= const* A(i,iam) end do return end subroutine order parameter (N=1000) C$OMP THREADPRIVATE(/buf/) common/buf/lft(N),rht(N) do i=1, N A(i,iam) = lft(index) end do return end buf 是线程内的公共块
  • 67. 2006年4月共享存储编程67/108Privateprivate(list)表明list中所列变量为组内线程私有变量. – 对变量不进行初始化(有构造函数除外) – 私有拷贝独立存储,不与原变量一致 main() { int i = 10000; #pragma omp parallel private(i) { i = -10000; } printf("%d\n",i); } 运行: 10000main() { int i = 10000; #pragma omp parallel { i = -10000; } printf("%d\n",i); } 运行: -10000
  • 68. 2006年4月共享存储编程68/108Private变量将在组内每一线程中进行创建,如果只有如果变量有构造函数,则调用构造函数进行初始化,否则该私有变量的值不确定. 在进入并行结构中,私有变量引用的原变量值是不确定的;也不能在并行构造对引用的变量进行修改,在结构中对这些对私有变量的修改不影响退出结构后的同名变量值. 私有变量应在并行性构造中进行初始化; 私有变量不能为引用类型(破坏了数据的独立性);
  • 69. 2006年4月共享存储编程69/108?For结构中:main(){ int i,sum=0,myid; #pragma omp parallel for \ private(i,sum) for(i=0;i<10;i++){ myid = omp_get_thread_num(); printf("%d\n",myid); sum = sum + 1; printf("%d\n",sum); } printf("sum = %d\n",sum); }3 804398705 2 804398705 2 804398706 2 804398707 0 804398705 0 804398706 0 804398707 1 804398705 1 804398706 1 804398707 sum = 10变量sum 未初始化.
  • 70. 2006年4月共享存储编程70/108FirstprivateFirstprivate是 private的特殊情况. – 在主线程中初始化每一个线程的私有拷贝变量. main(){ int i,sum=0,myid; #pragma omp parallel for firstprivate(i,sum) for(i=0;i<10;i++){ sum = sum + 1; printf("%d\n",sum); } printf("sum = %d\n",sum); }1 2 3 1 1 2 3 1 2 3:
  • 71. 2006年4月共享存储编程71/108LastprivateLastprivate将位于迭代末的私有变量转化为全局变量. #pragma omp parallel { #pragma omp for lastprivate(i) for (i=0; i
  • 72. 2006年4月共享存储编程72/108默认情形=default(shared)(因此不显示使用) 改变默认default(private): 将并行区内变量的默认类型改为私有,而不是共享 省去在每一并行区结构后加private(list) default(none): 指事先指定并行区中变量的默认类型 Fortran支持default(private). C/C++ 无default(private),只有default(shared) 和default(none).Default
  • 73. 2006年4月共享存储编程73/108DEFAULT例(Fortran)itotal = 1000 C$OMP PARALLEL DEFAULT(PRIVATE) SHARED(itotal) np = omp_get_num_threads() each = itotal/np ……… C$OMP END PARALLELitotal = 1000 C$OMP PARALLEL PRIVATE(np, each) np = omp_get_num_threads() each = itotal/np ……… C$OMP END PARALLEL
  • 74. 2006年4月共享存储编程74/108reduction归约操作(将各线程中处理结果经某种运算后送回到根进程) reduction (op : list). List中所列变量必须为并行区内的共享变量. 支持的运算操作: Operator Initialization + 0 * 1 && 1 || 0 仅支持标量的归约操作 #pragma omp parallel for private(i) shared(x, y, n) reduction(+: a, b) for (i=0; i
  • 75. 2006年4月共享存储编程75/108实例:PI求解通过数值积分的方法串行求解PI很简单的(具体代码见下一页). 基于OpenMP将该串行程序并行化.依据使用制导语句的不同,可有若干种方法(我们只讨论基于刚讲过的并行区与for结构): 仅用并行区结构将该程序改为一SPMD并行程序. 用任务分割结构进行并行化. 归约.
  • 76. 2006年4月共享存储编程76/108串行程序#include "stdio.h" main(int argc,char* argv[]) { … n = atoi(argv[1]); w = 1.0 / (double) n; sum = 0.0; for(i=0;i
  • 77. 2006年4月共享存储编程77/108仅基于Parallel块结构的并行程序 #include "stdio.h" main(int argc, char *argv[]){ … n = atoi(argv[1]); w = 1.0 / (double) n; sum = 0.0; #pragma omp parallel private(x) shared(w) reduction(+:sum) { myid = omp_get_thread_num(); numthreads = omp_get_num_threads(); for(i=myid;i
  • 78. 2006年4月共享存储编程78/108基于for结构的并行化代码 #include "stdio.h" main(int argc, char *argv[]) { … n = atoi(argv[1]); w = 1.0 / (double) n; sum = 0.0; #pragma omp for reduction(+:sum) for(i=0;i
  • 79. 2006年4月共享存储编程79/108OpenMP结构化块类型OpenMP主要有五类结构化块: 并行区Parallel Regions 任务分割Worksharing 数据环境Data Environment 同步Synchronization 运行时函数/环境变量
  • 80. 2006年4月共享存储编程80/108OpenMP同步共享变量读写 单一文件I/O OpenMP提共下列同步结构: – atomic – critical section – barrier – flush – ordered – single – master 这两个结构实质上不属于同步结构,是并行区结构和任务分割结构中的内容.
  • 81. 2006年4月共享存储编程81/108critical section同时至多有一个线程进入临界区. #pragma omp critical [(name)] new-line structured-block C$OMP PARALLEL DO PRIVATE(B) C$OMP& SHARED(RES) DO 100 I=1,NITERS B = DOIT(I) C$OMP CRITICAL CALL CONSUME (B, RES) C$OMP END CRITICAL 100 CONTINUE
  • 82. 2006年4月共享存储编程82/108atomicAtomic是临界区的特例,主要用于某些简单的语句. #pragma omp atomic new-line expression-stmt 其中expression-stmt只能为下列情形: x binop = expr (bino只能是+ * - / & ^ | << >> ) x++ ++x x-- --x 仅用于内存变量的更新(在下面的例子中即对X的更新) C$OMP PARALLEL PRIVATE(B) B = DOIT(I) C$OMP ATOMIC X = X + B C$OMP END PARALLEL
  • 83. 2006年4月共享存储编程83/108下面两段代码区别是什么?#pragma omp parallel for \ shared(x, y, index, n) for (i=0; i
  • 84. 2006年4月共享存储编程84/108原子操作和临界区比较 原子性是指操作的不可再分性,OpenMP利用原子结构主要是用于防止多线程对内存的同一地址的并发写. 临界区可以完成所有的原子操作. 原子结构可更好被编译优化: 有硬件指令可用于实现原子操作,而且系统开销也很小. 原子操作与并发并不矛盾, 临界区一定是串行执行的,原子操作不一定是串行执行
  • 85. 2006年4月共享存储编程85/108barrierBarrier: 每一线程等待直至所有组内其它线程执行到Barrier为止. #pragma omp parallel shared (A, B, C) private(id) { id=omp_get_thread_num(); A[id] = big_calc1(id); #pragma omp barrier #pragma omp for for(i=0;i
  • 86. 2006年4月共享存储编程86/108Ordered用于指定循环中各线程执行任务的顺序严格与顺序方式时的执行顺序一致. 以制导语句,而不是子句出现的ordered,只能出现在并行区动态范围内 如:在并行区中按顺序方式打印输出:orderedvoid worker(int k){ #pragma omp ordered printf(“%d ”,k); } main(){ int i; #pragma omp parallel for schedule(dynamic) for(i=0;i<5;i++) worker(i); } 运行结果: 0 1 2 3 4
  • 87. 2006年4月共享存储编程87/108masterMaster结构用于标志一个结构块只能由主线程执行. 其它线程跨越该块.该结构无隐式barrier同步. #pragma omp parallel private (tmp) { do_many_things(); #pragma omp master { exchange_boundaries(); } #pragma barrier do_many_other_things(); }
  • 88. 2006年4月共享存储编程88/108singleSingle结构用于标志一个块内的代码仅能由一个线程执行(不一定是主线程). 在single块后有一隐式的barrier同步操作. #pragma omp parallel{ #pragma omp single printf("Beginning work1.\n"); work1(); #pragma omp single printf("Finishing work1.\n"); #pragma omp single nowait printf("Finished work1 and beginning work2.\n"); work2(); }最早遇到single块者执行同步同步不同步
  • 89. 2006年4月共享存储编程89/108flush#pragma omp flush [(list)] new-line !$OMP FLUSH [(list)] Flush语句可显式或隐式执行.用于在程序的某一点处,确定共享内存中的变量对各线程的应印象是一致的. 如:编译器必须将寄存器中的值写回内存,硬件刷新缓冲区等.
  • 90. 2006年4月共享存储编程90/108Flush(Fortran)隐式flush BARRIER CRITICAL and END CRITICAL END DO END SECTIONS END SINGLE END WORKSHARE ORDERED and END ORDERED PARALLEL and END PARALLEL PARALLEL DO and END PARALLEL DO PARALLEL SECTIONS and END PARALLEL SECTIONS PARALLEL WORKSHARE and END PARALLEL WORKSHARE 下列语句无隐式flush DO MASTER and END MASTER SECTIONS SINGLE WORKSHARE
  • 91. 2006年4月共享存储编程91/108Flush(C)隐式flush barrier Critical区的出入口 Ordered区的出入口 Parallel的出口 For的出口 Sections的出口 Single的出口 无隐式flush no wait
  • 92. 2006年4月共享存储编程92/108隐式同步下列OpenMP结构之后有隐式Barrier: end parallel end do (用nowait禁止) end sections (用nowait禁止) end critical end single (用nowait禁止)
  • 93. 2006年4月共享存储编程93/108Nesting 并行结构的嵌套一个parallel语句可以出现在嵌套在另一个parallel内.如果不明确指定嵌套并行性,则该并行区的线程组只由当前的线程组成,否则,该并行区可建立自己的线程组. 同一parallel内的for, sections, 和 single语句不允许相互嵌套. 同名critical语句不允许相互嵌套. for, sections, 和 single 不能出现在critical, ordered, 和 master 的动态范围中. barrier 语句不能出现在 for, ordered, sections, single, master, 和 critical 的动态范围中. master 不能出现在 for, sections, 和 single 的动态范围内. ordered 不能出现在 critical的动态范围内.
  • 94. 2006年4月共享存储编程94/108OpenMP结构化块类型OpenMP主要有五类结构化块: 并行区Parallel Regions 任务分割Worksharing 数据环境Data Environment 同步Synchronization 运行时函数/环境变量
  • 95. 2006年4月共享存储编程95/108OpenMP函数锁函数 omp_init_lock(), omp_set_lock(), omp_unset_lock(), omp_test_lock(), omp_destroy_lock() 运行时环境函数: – 改变或检查线程数 omp_set_num_threads(), omp_get_num_threads(), omp_get_thread_num(), omp_get_max_threads() – 开/关嵌套和动态代码 omp_set_nested(), omp_set_dynamic(), omp_get_nested(), omp_get_dynamic() – 当前代码位于并行区内吗? omp_in_parallel() – 机器有多少个处理器? omp_get_num_procs()
  • 96. 2006年4月共享存储编程96/108例:锁的使用下面的例子是用来显示锁的使用: 锁对象具有omp_lock_t,在锁使用前需对锁进行初始化: omp_init_lock(). 当一个线程试图获得锁以进入第一个临界区时,处理于空闲状态,以等待进入临界区. 一旦得到锁,则对临界区加锁: omp_set_lock(),退出完临界区时解锁omp_unset_lock(). 在进入第二个临界区时,则通过非阻塞函数omp_test_lock()来测试可能性,同时做一些其它的事情. 在锁不再使用时,用omp_destroy_lock()销毁.
  • 97. 2006年4月共享存储编程97/108#include int main() { omp_lock_t lck; int id; omp_init_lock(&lck); #pragma omp parallel shared(lck) private(id) { id = omp_get_thread_num(); omp_set_lock(&lck); printf("My thread id is %d.\n", id); omp_unset_lock(&lck); while (! omp_test_lock(&lck)) { skip(id); /* we do not yet have the lock, so we must do something else */ } work(id); /* we now have the lock and can do the work */ omp_unset_lock(&lck); } omp_destroy_lock(&lck); }
  • 98. 2006年4月共享存储编程98/108锁:保护共享资源omp_lock_t lck; omp_init_lock(&lck); #pragma omp parallel private (tmp) { id = omp_get_thread_num(); tmp = do_lots_of_work(id); omp_set_lock(&lck); printf(“%d %d”, id, tmp); omp_unset_lock(&lck); }
  • 99. 2006年4月共享存储编程99/108设定动态模式,由程序来指定线程数(一般默认线程数与处理器个数相等). #include void main() { omp_set_dynamic(0); omp_set_num_threads(4); #pragma omp parallel { int id=omp_get_thread_num(); do_lots_of_stuff(id); } }
  • 100. 2006年4月共享存储编程100/108指定线程数omp_set_dynamic(0); omp_set_num_threads(16); #pragma omp parallel shared(x, npoints) private(iam, ipoints) { if (omp_get_num_threads() != 16) abort(); iam = omp_get_thread_num(); ipoints = npoints/16; do_by_16(x, iam, ipoints); }
  • 101. 2006年4月共享存储编程101/108得到线程数int numthreads; numthreads = omp_get_num_threads(); printf(“%d\n”,numthreads); #pragma omp parallel private(i) { numthreads = omp_get_num_threads(); printf(“%d\n”,numthreads); } 运行: 1 4 4 4 4
  • 102. 2006年4月共享存储编程102/108环境变量线程任务调度控制 – OMP_SCHEDULE “SCHEDULE[, CHUNK_SIZE]” 设定缺省线程数 – OMP_NUM_THREADS INT_LITERAL 是否允许各并行区内线程数目不相同(动态生成线程组)? – OMP_DYNAMIC TRUE || FALSE 动态模式 (默认): 并行区中的线程数动态确定,各并行区可具有不同的线程数. 此时,线程数设置函数omp_set_num_threads()只设定一上限,实际中发生的线程数可能要比我们设置的数目少. 静态模式: 由程序员确定并行区中线程的数量. 嵌套并行区是否建立新的线程组或它们是否要串行化?(仅由线程组中的主线程执行,如I/O操作等) – OMP_NESTED TRUE || FALSE(默认为FALSE)
  • 103. 2006年4月共享存储编程103/108汇总并行区 parallel 任务分割 for sections Single parallel for parallel sections 同步 master critical barrier atomic flush ordered并行区 parallel 任务分割 DO SECTIONS SINGLE WORKSHARE PARALLEL DO PARALLEL SECTIONS PARALLEL WORKSHARE 同步 MASTER CRITICAL BARRIER ATOMIC FLUSH ORDERED
  • 104. 2006年4月共享存储编程104/108运行环境函数汇总 omp_set_num_threads omp_get_num_threads omp_get_max_threads omp_get_thread_num omp_get_num_procs omp_in_parallel omp_set_dynamic omp_get_dynamic omp_set_nested omp_get_nestedOMP_SET_NUM_THREADS OMP_GET_NUM_THREADS OMP_GET_MAX_THREADS OMP_GET_THREAD_NUM OMP_GET_NUM_PROCS OMP_IN_PARALLEL OMP_SET_DYNAMIC OMP_GET_DYNAMIC OMP_SET_NESTED OMP_GET_NESTED OMP_GET_WTIME OMP_GET_WTICK
  • 105. 2006年4月共享存储编程105/108锁函数汇总 omp_init_lock omp_init_nest_lock omp_destroy_lock omp_destroy_nest_lock omp_set_lock omp_set_nest_lock omp_unset_lock omp_unset_nest_lock omp_test_lock omp_test_nest_lockOMP_INIT_LOCK OMP_INIT_NEST_LOCK OMP_DESTROY_LOCK OMP_DESTROY_NEST_LOCK OMP_SET_LOCK OMP_SET_NEST_LOCK OMP_UNSET_LOCK OMP_UNSET_NEST_LOCK OMP_TEST_LOCK OMP_TEST_NEST_LOCK
  • 106. 2006年4月共享存储编程106/108环境变量(C和Fortran一样) OMP_SCHEDULE OMP_NUM_THREADS OMP_DYNAMIC OMP_NESTED
  • 107. 2006年4月共享存储编程107/108OpenMP特点总结支持C,C++和Fortran 共享存储并行机并行编程的主流平台,获得并行机厂商和用户的广泛支持 特别适合于串行程序的逐步并行化 如:先进行一些特别耗时的大循环的并行化,然后再逐步进行再大粒度的并行. Orphan制导语句的支持,使得OpenMP较之于以前的共享存储编程模型更适合粗粒度的并行
  • 108. 2006年4月共享存储编程108/108谢谢!