Presentation is loading. Please wait.

Presentation is loading. Please wait.

机群应用开发 并行编程原理及 程序设计 Parallel Programming: Fundamentals and Implementation 马少杰 mashj@sugon.com 曙光信息产业有限公司 2011.7.

Similar presentations


Presentation on theme: "机群应用开发 并行编程原理及 程序设计 Parallel Programming: Fundamentals and Implementation 马少杰 mashj@sugon.com 曙光信息产业有限公司 2011.7."— Presentation transcript:

1 机群应用开发 并行编程原理及 程序设计 Parallel Programming: Fundamentals and Implementation
马少杰 曙光信息产业有限公司 2011.7

2 参考文献 黄铠,徐志伟著,陆鑫达等译. 可扩展并行计算技术,结构与编程. 北京:机械工业出版社, 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. 2018年11月 共享存储编程

3 相关网址 MPI: http://ww.mpi-forum.org, http://www.mcs.anl.gov/mpi
Pthreads: PVM: OpemMP: 网上搜索: 2018年11月 共享存储编程

4 共享存储编程 Programming with Shared Memory
2018年11月 共享存储编程

5 共享存储并行机模型 体系结构特点: 多台处理机通过互联网络共享一个统一的内存空间,通过单一内存地址来实现处理机间的协调.
内存空间也可由多个存储器模块构成. 每台处理机可以执行相同或不同的指令流,每台处理机可以直接访问到所有数据. 处理机间通信是借助于共享主存来实现的. 可扩展性差,当处理机需要同时访问共享全局变量时,产生内存竞争现象而严重影响效率,比较适合中小规模应用问题的计算和事务处理. 2018年11月 共享存储编程

6 共享存储编程标准与特点 共享存储器编程标准 Pthreads(线程标准) X3H5(线程标准)
OpenMP(最常用的共享存储并行编程方式,是我们讨论的重点.) 共享存储器编程特点 显式多线程库调用.(Pthreads). 编译制导语句,OpenMP等. 语言 C,Fortran77,Fortran90/95,C++… 2018年11月 共享存储编程

7 并行编程标准 线程库标准(Thread Library) – Win32 API. – POSIX threads线程模型.
– X3H5:概念性线程模型 编译制导(Compiler Directives) – OpenMP - portable shared memory parallelism. 2018年11月 共享存储编程

8 为什么流行多线程编程? 线程:在进程的内部执行的指令序列. 相对于进程,线程开销小:
创建一个线程的时间大约是建立一个新进程的1/30。如在Sun4/75工作上站上,创建一个非绑定线程约为52微秒,而fork()一次的时间为1700微秒。 线程同步时间约是进程同步时间的1/3. 线程与RPC相结合,发挥多处理机的处理能力; 发挥多处理器的处理能力; 开发程序的并发性,改善程序的结构. 容易实现数据共享:由于线程共用内存地址,因此可实现数据共享 例:一高性能Web服务器可为每一打开链接的浏览器分配一个线程,所有线程即可共用同一cache来访问网站的热点话题 统一的标准: 以前各开发商提供互不兼容的线程库,结果导致多线程程序不能很好地移值。自1995年的POSIX线程标准实施之后,极大地促进多线程编程的统一。各系统都支持Pthreads,如Linux、SUN、IBM AIX等。 2018年11月 共享存储编程

9 Pthreads线程模型 POSIX1003.4a小组研究多线程编程标准. 当标准完成后,大多数支持多线程的系统都支持POSIX接口.很好的改善了多线程编程的可移植性. IEEE Portable Operating System Interface, POSIX, 标准:POSIX线程模型:pthreads. 2018年11月 共享存储编程

10 线程管理(Pthread为例) 创建:pthread_create 终止:pthread_exit 汇合:pthread_join
分离:pthread_detach 线程属性初始化:pthread_attr_init 唯一执行:pthread_once 2018年11月 共享存储编程

11 同步对象 在共享存储多处理器并行机上,线程通过全局变量通信,对于全局变量的操作必须进行同步。
pthread提供两个线程同步原语 : 互斥和条件变量. 2018年11月 共享存储编程

12 互斥锁函数 函数 操作 Mutex_init() 初始化一个互斥锁 Mutext_lock() 阻塞式加锁操作
函数 操作 Mutex_init() 初始化一个互斥锁 Mutext_lock() 阻塞式加锁操作 Mutex_trylock() 非阻塞式加锁操作 Mutex_unlock() 解锁 Mutex_destroy() 解除互斥状态 2018年11月 共享存储编程

13 条件变量的函数 函数 操作 pthread_cond_init() 初始化条件变量 pthread_cond_wait() 阻塞直至条件为真
函数 操作 pthread_cond_init() 初始化条件变量 pthread_cond_wait() 阻塞直至条件为真 pthread_cond_signal() 强制条件为真,解除等待条件的线程的阻塞 pthread_cond_timedwait() 阻塞直到指定条件为真或timeout pthread_cond_broadcast() 解除所有等待条件的线程的阻塞 pthread_cond _destroy() 销毁条件变量 2018年11月 共享存储编程

14 Hello World(1) 编译命令 gcc hello.c –lpthread 运行结果 Hello World!
#include <pthread.h> #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! 2018年11月 共享存储编程

15 pthread_create(&thread,NULL,worker,NULL);
pthread_t 线程数据类型 pthread_create(&thread,NULL,worker,NULL); 函数pthread_create()用于创建一新的线程,新线程一旦建立便进入运行状态 参数: 线程指针或句柄 线程属性变量,属性参数:默认为NULL. 属性对象一旦建立可以用于创建多个具有共同属性的线程,线程创建后,可删除属性对象. 线程要执行的函数 传入该执行函数的一个参数,无则NULL.可以是任意类型 线程的终止 线程函数正常终止返回; 自调用pthear_exit()函数; 线程被取消; 线程接收到中止的信号; 当主进程执行exit()后,进程及其全部线程全部终止. 2018年11月 共享存储编程

16 pthread_join(pthread_t wait_for,void** status); 等待直到线程结束;
等与被等的两线程必须是同一进程内部的线程(而且不是分离线程); 返回值 0 成功返回 ESRCH 参数wait_for指定的线程不存在或是一分离线程; EINVAL 线程参数无效; EDEADLK 等待自身结束. 不能有两个线程同时等待同一个线程的结束, 否则其中一个线程正常返回,另外一个返回ESRCH错误. 2018年11月 共享存储编程

17 Hello World(2) Hello World from thread 0! Hello World from thread 1!
#include <pthread.h> #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<numthrds;i++) pthread_create(&tid[i],NULL,worker,NULL); for(i=0;i<numthrds;i++) pthread_join(tid[i],NULL); } void *worker(){ int myid; myid = pthread_self() - tid[0]; printf("Hello World from thread %d!\n",myid); Hello World from thread 0! Hello World from thread 1! Hello World from thread 2! Hello World from thread 3! Hello World from thread 4! 2018年11月 共享存储编程

18 Hello World(3) #include <pthread.h> #include "stdio.h"
void *worker(){ int myid,num; myid = pthread_self() - tid[0]; printf("%d was added to the sum in thread %d\n",myid*10,myid); pthread_mutex_lock(&mutex); sum += myid*10; pthread_mutex_unlock(&mutex); return; } #include <pthread.h> #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<numthrds;i++) pthread_create(&tid[i],NULL,worker,NULL); for(i=0;i<numthrds;i++) pthread_join(tid[i],NULL); printf(“The sum is %d\n",sum); } 2018年11月 共享存储编程

19 运行结果 0 was added to the sum in thread 0
The sum is 100 2018年11月 共享存储编程

20 基于多线程编程的PI求解 2 2018年11月 共享存储编程

21 #include <pthread.h>
#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); 2018年11月 共享存储编程

22 gcc hello.c –lpthread a.out 1000 5 computed pi = 3.1415927369231271
转去Openmp void 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<num_threads; i++){ if(pthread_create(&tid[i],NULL,PIworker,NULL)){ fprintf(stderr,"Cannot create thread %d\n",i); /*join threads*/ for(i = 0; i < num_threads; i++) pthread_join(tid[i],NULL); printf("computed pi = %.16f\n",pi); 2018年11月 共享存储编程

23 多线程并行编程特点 pthread_create()创建一个新线程比重新启动一个线程花费的时间少: 需要时创建+任务结束立刻杀掉 vs. 维护一大堆的空闲线程并且相互切换. 在加锁的前提下访问共享资源 不支持数据并行,适合于任务级并行,即一个线程单独执行一个任务; 不支持增量并行化,对于一个串行程序,很难用Pthreads进行并行化 Pthreads主要是面向操作系统, 而不是为高性能计算设计的,因此不是并行计算程序设计的主流平台。但是“多线程并发执行”这种思想却被广泛地应用于高性能计算。这就是我们即将要讲的共享存储并行编程的另外一种被并行机制造商和广用并行计算用户广泛接受的平台:OpenMP 2018年11月 共享存储编程

24 并行编程标准 线程库标准(Thread Library) – Win32 API. – POSIX threads线程模型.
– X3H5:概念性线程模型 编译制导(Compiler Directives) – OpenMP - portable shared memory parallelism. 2018年11月 共享存储编程

25 www.openmp.org An Industry Standard API for Shared Memory Programming
An API for Writing Multithreaded Applications 一系列编译制导语句和库函数 使得Fortran, C and C++的多线程编程更加容易 2018年11月 共享存储编程

26 与X3H5的关系 X3H5是ANSI/X3授权的小组委员会,主要目的是在PCF(the Parallel Computing Forum)工作的基础上,发展并行计算的一个ANSI标准. PCF是一非正式的工业组织,虽在DO循环的并行化方法的标准化方面做一些工作,但在起草拟了一个标准后就草草收场. OpenMP专门针对这类并行化问题,并完成了这项工作,同时得到工业界的广泛支持. 2018年11月 共享存储编程

27 ANSI X3H5共享编程标准 并行块(分散任务Work Sharing) 并行循环 单进程 概念性的编程模型(ANSI标准(1993))
没有任何商品化的共享存储器系统依附于X3H5,但X3H5的基本概念影响以后共享存储器系统的并行编程.(一些基本概念在OpenMP均出现!) X3H5支持C,Fortran77以及Fortran90语言. X3H5规定的基本的并行结构用于并行性表述: 并行块(分散任务Work Sharing) 并行循环 单进程 parallel { } end parallel psections { } end psections pdo { } end pdo psingle { } end psingle 2018年11月 共享存储编程

28 X3H5编程实例 各线程以负载平衡方式分担任务 可能为:F(1:1),F(2:2),F(3:6)…
program main !程序以顺序模式执行 A !A只由基本线程执行 parallel !转换成并行模式 B !B为每个组员所复制 psections !并行块开始 section C !一个组员执行C 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 线程 P Q R 隐式barrier B B B C D 隐式barrier E 隐式barrier F(1:2) F(3:4) F(5:6) 无隐式barrier G G G 隐式barrier H 各线程以负载平衡方式分担任务 可能为:F(1:1),F(2:2),F(3:6)… 2018年11月 共享存储编程

29 X3H5例程执行过程描述 …... … fork barrier
程序以顺序方式启动,此时只有一个初始化线程,称为基本线程或主线程.当程序遇到parallel时,通过派生多个子线程转换为并行执行模式(线程数隐式决定).基本线程与它的子线程形成一个组.所有组员并行处理后继并行代码,直至end parallel.然后程序转为顺序模式,只有基本线程继续执行. 子线程遇到内部并行或任务分担构造时,可以继续派生其子线程,从而成为一个新组的基本线程. 线程间同步,通信与交互 隐式路障:parallel, end parallel, end pdo或end psingle处隐式barrier.如果不需,则加no wait; 各处理机通过全局变量通信,通过私有变量封装数据 顺序执行 fork …... 并行执行 barrier 顺序执行 2018年11月 共享存储编程

30 OpenMP: 并行模型 Fork-Join 并行模式: 主线程根据需要创建一组子线程进行工作分担. 可对串行程序进行逐步并行化. 主线程
并行执行区域 2018年11月 共享存储编程

31 如何应用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]); } 串行程序 并行程序 2018年11月 共享存储编程

32 线程间如何交互? OpenMP 是基于共享内存模型. 线程通过共享变量通信. 访问共享变量会导致race condition (竞态状态)
为了避免线程进入竞态状态: 通过同步对象来保护数据冲突. 2018年11月 共享存储编程

33 OpenMP术语 C$OMP CONSTRUCT [clause [clause]…]
大多OpenMP构造是制导语句或pragmas. C和C++的pragmas形式为: #pragma omp construct [clause [clause]…] Fortran中,制导语句形式为以下几种: C$OMP CONSTRUCT [clause [clause]…] !$OMP CONSTRUCT [clause [clause]…](自由书写格式唯一) *$OMP CONSTRUCT [clause [clause]…] 例:以下三种等价(第一行为列数) C !$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的编译器编译后,仍为串行程序. 2018年11月 共享存储编程

34 Structured 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 一个结构化块 一个非结构化块 2018年11月 共享存储编程

35 OpenMP结构化块类型 OpenMP主要有五类结构化块: 并行区Parallel Regions 任务分割Worksharing
数据环境Data Environment 同步Synchronization 运行时函数/环境变量 在Fortran,C/C++中,OpenMP基本上是一样的. 2018年11月 共享存储编程

36 Parallel 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). 2018年11月 共享存储编程

37 并行区的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 end poo.f C$OMP PARALLEL call whoami C$OMP END PARALLEL + Static/lexical extent:在书写上直接包含在并行区内的部分. Dynamic extent:包括并行区内直接和间接(函数调用)包含的内容,也被称为region. Orphan制导语句:落在子程序中的制导语句,方便于子程序的并行化,免去传统的inline处理 2018年11月 共享存储编程

38 所有线程在此处同步(如,隐式barrier同步)
并行区代码流程 double A[1000]; omp_set_num_threads(4); #pragma omp parallel { int ID = omp_thread_num(); worker(ID,A); } 每一线程执行相同代码,不同数据. Double A[1000] opm_set_num_threads(4) worker(0,A) worker(1,A) worker(2,A) worker(3,A) 所有线程在此处同步(如,隐式barrier同步) 所以,并行区结构也被称为SPMD结构. 共享存储编程 2018年11月

39 Hello World(C) #include <omp.h> 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); } 2018年11月 共享存储编程

40 Hello 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 2018年11月 共享存储编程

41 OpenMP并行程序编译 支持编译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 2018年11月 共享存储编程

42 一些细节(可先不关心) 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):归约操作 2018年11月 共享存储编程

43 OpenMP结构化块类型 OpenMP主要有五类结构化块: 并行区Parallel Regions 任务分割Worksharing
DO(Fortran)/for(C)结构:针对循环的并行化结构 Sections:代码段间的并行 Single:强制并行区中某些代码以串行方式执行(如:I/O) 数据环境Data Environment 同步Synchronization 运行时函数/环境变量 OpenMP最重要部分 2018年11月 共享存储编程

44 循环分割:DO(Fortran)/for(C)结构
!$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<n;i++){ } 在DO/for结构之后有一隐式barrier同步操作,用NO WAIT/no wait可以禁止. 2018年11月 共享存储编程

45 比较parrallel构造与for构造 串行代码 用并行区实现并行化 用任务分割构造实现并行化
for(i=0;I<N;i++) { a[i] = a[i] + b[i];} #pragma omp parallel { int id, i, Nthrds, istart, iend; id = omp_get_thread_num(); Nthrds = omp_get_num_threads(); istart = id * N / Nthrds; iend = (id+1) * N / Nthrds; for(i=istart;I<iend;i++) { a[i] = a[i] + b[i];} } 用并行区实现并行化 #pragma omp parallel #pragma omp for schedule(static) for(i=0;I<N;i++) { a[i] = a[i] + b[i];} 用任务分割构造实现并行化 对于DO结构与PARALLEL结构的比较同理,且以后讨论若无特别说明均基于C描述. 2018年11月 共享存储编程

46 并行区与任务分割间的关系 根进程 并行区 并行区和任务分割是OpenMP两类基本的并行性构造;
并行区中的代码对组内的各线程是可见的,也即并行区内的代码由各线程同时执行; 任务分割与并行区不同,它是将一个整体任务按负载平衡的方式分配给各线程来互相配合完成. 并行区是并行的先决条件,任务分割必须要与并行区一起使用才能生效; 并行区构造为!omp parallel; 任务分割构造有:do/for,section,和single三种. 根进程 并行区 2018年11月 共享存储编程

47 更详细的for语法 #pragma omp for [clause[ clause] ... ] new-line for-loop
private(list) firstprivate(list) lastprivate(list) reduction(operator: list) ordered schedule(kind[, chunk_size]) nowait 后面将详细说明 2018年11月 共享存储编程

48 更详细的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 2018年11月 共享存储编程

49 DO/for使用注意事项 循环体必须紧接在DO或for之后. For循环必须为一结构化块,且其执行不被break语句中断.
在Fortran中,如果写上END DO制导语句,其必须要紧跟在DO循环的结束之后. 循环变量必须为整形. Schedule, ordered,nowait子句只能出现一次. 2018年11月 共享存储编程

50 schedule Schedule子名决定循环如何在各线程中进行分配: 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以循环次数为单位. 示意图见下页. 2018年11月 共享存储编程

51 Schedule示意图 Work pool Work Work .. .. .. .. .. . . . 执行时间 . . .
Dynamic方式 Static方式 Guided方式 2018年11月 共享存储编程

52 适用条件 静态:适用于大部分情形. 特点: 各线程任务明确,在任务分配时无需同步操作. 运行快的线程需等慢的线程为:
动态:适用于任务数量可变或不确定的情形(如条件收敛循环). 各线程将要执行的任务不可预见,任务分配需同步操作. Guided:线程异步到达for结构 首先到达的线程总是分得q=ceiling(n/p)次循环,然后n=max(n-q,p*k),循环分配,直到n=p*k为止. 环境变量:无需重新编译程序,可根据原始输入数据的情况改变任务分配策略. 2018年11月 共享存储编程

53 NOWAIT子句(Fortran) END DO必须紧随enddo,可省略. 默认循环变量i为私有线程私有类型变量
C$OMP PARALLEL C$OMP DO do i=1,n a(i)= cos(a(i)) enddo C$OMP END DO b(i)=a(i)+b(i) C$OMP END PARALLEL C$OMP PARALLEL C$OMP DO do i=1,n a(i)= cos(a(i)) enddo C$OMP END DO NOWAIT b(i)=a(i)+b(i) C$OMP END DO C$OMP END PARALLEL 隐式 BARRIER No BARRIER END DO必须紧随enddo,可省略. 2018年11月 共享存储编程

54 no wait子句(C) {…}为并行区. #pragma omp parallel { #pragma omp for
for(i=1;i<n;i++) a(i)= cos(a(i)); b(i)=a(i)+b(i); } #pragma omp parallel { #pragma omp for no wait for(i=1;i<n;i++) a(i)= cos(a(i)); b(i)=a(i)+b(i); } No BARRIER 隐式 BARRIER {…}为并行区. 2018年11月 共享存储编程

55 Sections构造(非循环并行) Sections用于程序中大范围的非迭代执行代码段间的并行化.(如前10行和后10行间代码间无依赖关系,可以并行.) #pragma omp sections [no wait] { x_calculation(); #pragma omp section y_calculation(); z_calculation(); } x_calculation(),y_calculation()以及z_calculation()代表三部分之间无依赖关系 的非循环代码段.实质上它们各代表很多行代码. 缺省时每一个“omp sections”构造结束后有一个barrier同步操作.通过使用 “nowait” 子句禁止隐式barrier同步(在构造语句后直接加即可). 与for结构相类似,OpenMP也提供parallel sections. 2018年11月 共享存储编程

56 Single Single:强制并行区中某些代码以串行方式执行
#pragma omp single [clause[ clause] ...] new-line structured-block Clause is one of the following: private(list) firstprivate(list) nowait 2018年11月 共享存储编程

57 Work sharing语句汇总 Fortran DO SECTIONS SINGLE WORKSHARE C for sections
2018年11月 共享存储编程

58 Binding并行结构的联合使用 for, sections, single, master, and barrier如果不位于并行区内或不与并行区联合使用,便不起任何作用. #pragma omp parallel for for (I=0;I<N;I++){ NEAT_STUFF(I); } #pragma omp parallel #pragma omp for for (I=0;I<N;I++){ NEAT_STUFF(I); } = 2018年11月 共享存储编程

59 Fortran !$OMP PARALLEL DO [clause[[,] clause]...] do_loop
[!$OMP END PARALLEL DO] !$OMP PARALLEL SECTIONS [clause[[,] clause]...] [!$OMP SECTION ] block [!$OMP SECTION block] . . . !$OMP END PARALLEL SECTIONS !$OMP PARALLEL WORKSHARE [clause[[,] clause]...] block !$OMP END PARALLEL WORKSHARE 2018年11月 共享存储编程

60 C #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 . .] } 2018年11月 共享存储编程

61 OpenMP结构化块类型 OpenMP主要有五类结构化块: 并行区Parallel Regions 任务分割Worksharing
数据环境Data Environment 同步Synchronization 运行时函数/环境变量 2018年11月 共享存储编程

62 默认数据类型 共享变量编程模型: – 大部分变量默认为共享类型 各线程共享全局变量
– Fortran: COMMON 块, SAVE 变量, MODULE 变量 – C: File scope variables, static 但并不是所有变量全部共享... – 在并行区内调用的子程序中的栈变量是私有 – 在语句块中的Auto变量为私有变量. 2018年11月 共享存储编程

63 举例说明变量类型 program sort common /input/ A(10) integer index(10)
call input C$OMP PARALLEL call work(index) C$OMP END PARALLEL print*, index(1) subroutine work common /input/ A(10) real temp(10) integer count save count ………… 变量A,index和cound为所有线程共享. temp为线程私有变量,各线程间到不可见. 2018年11月 共享存储编程

64 变量类型改变 变量类型编译制导: threadprivate:将某些全局变量或数据区变为线程私有. 通过下列类型属性子句来改变变量类型:
– shared(并行区结构) – private – firstprivate – lastprivate:循环体内的私有变量可以转变为全局变量, 在循环结束后访问; 默认数据类型状态可以改变: – default (private | shared | none) 2018年11月 共享存储编程

65 私有类型变量 变量私有化是OpenMP采用的重要的编译技术. 变量私有化; 私有变量初始化: Copyin Firstprivate
私有变量返回: Reduction Lastprivate OpenMP私有类型变量是指并行区内的只能被线程组内的某一线程访问的变量. OpenMP的私有变量包括: 并行区内定义的变量; 由threadprivate 制导语句指明的变量; 由private, firstprivate,lastprivate, or reduction 子句指定的变量; 循环控制变量. 2018年11月 共享存储编程

66 Threadprivate: 线程的全局私有变量
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 buf 是线程内的公共块 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 2018年11月 共享存储编程

67 Private private(list)表明list中所列变量为组内线程私有变量. – 对变量不进行初始化(有构造函数除外)
– 私有拷贝独立存储,不与原变量一致 main() { int i = 10000; #pragma omp parallel private(i) i = ; } printf("%d\n",i); 运行: 10000 main() { int i = 10000; #pragma omp parallel i = ; } printf("%d\n",i); 运行: -10000 2018年11月 共享存储编程

68 Private变量将在组内每一线程中进行创建,如果只有如果变量有构造函数,则调用构造函数进行初始化,否则该私有变量的值不确定.
在进入并行结构中,私有变量引用的原变量值是不确定的;也不能在并行构造对引用的变量进行修改,在结构中对这些对私有变量的修改不影响退出结构后的同名变量值. 私有变量应在并行性构造中进行初始化; 私有变量不能为引用类型(破坏了数据的独立性); 2018年11月 共享存储编程

69 ? For结构中: main(){ int i,sum=0,myid; #pragma omp parallel for \
3 2 1 sum = 0 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); 变量sum 未初始化. 2018年11月 共享存储编程

70 Firstprivate Firstprivate是 private的特殊情况. – 在主线程中初始化每一个线程的私有拷贝变量. : 1
2 3 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); : 2018年11月 共享存储编程

71 Lastprivate Lastprivate将位于迭代末的私有变量转化为全局变量. #pragma omp parallel
for (i=1;i<n-1;i++) a(i)= b(i+1) a(i) = b(0) #pragma omp parallel { #pragma omp for lastprivate(i) for (i=0; i<n-1; i++) a[i] = b[i] + b[i+1]; } a[i]=b[0]; 顺序执行 i=n-1 2018年11月 共享存储编程

72 Default 默认情形=default(shared)(因此不显示使用) 改变默认default(private):
将并行区内变量的默认类型改为私有,而不是共享 省去在每一并行区结构后加private(list) default(none): 指事先指定并行区中变量的默认类型 Fortran支持default(private). C/C++ 无default(private),只有default(shared) 和default(none). 2018年11月 共享存储编程

73 DEFAULT例(Fortran) itotal = 1000 C$OMP PARALLEL PRIVATE(np, each)
np = omp_get_num_threads() each = itotal/np ……… C$OMP END PARALLEL itotal = 1000 C$OMP PARALLEL DEFAULT(PRIVATE) SHARED(itotal) np = omp_get_num_threads() each = itotal/np ……… C$OMP END PARALLEL 2018年11月 共享存储编程

74 reduction 归约操作(将各线程中处理结果经某种运算后送回到根进程) reduction (op : list).
支持的运算操作: Operator Initialization + 0 * 1 && 1 || 0 仅支持标量的归约操作 #pragma omp parallel for private(i) shared(x, y, n) reduction(+: a, b) for (i=0; i<n; i++) { a = a + x[i]; b = b + y[i]; } 2018年11月 共享存储编程

75 实例:PI求解 通过数值积分的方法串行求解PI很简单的(具体代码见下一页).
基于OpenMP将该串行程序并行化.依据使用制导语句的不同,可有若干种方法(我们只讨论基于刚讲过的并行区与for结构): 仅用并行区结构将该程序改为一SPMD并行程序. 用任务分割结构进行并行化. 归约. 2018年11月 共享存储编程

76 串行程序 #include "stdio.h" main(int argc,char* argv[]) { …
n = atoi(argv[1]); w = 1.0 / (double) n; sum = 0.0; for(i=0;i<n;i++){ x = w * ((double)i - 0.5); sum = sum + 4.0/(1.0+x*x); } pi = w * sum; printf("Computed pi = %.16f\n",pi); 2018年11月 共享存储编程

77 仅基于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<n;i+=numthreads){ x = w * ((double)i - 0.5); sum = sum + 4.0/(1.0+x*x); } …. 类似于多线程编程 2018年11月 共享存储编程

78 基于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<n;i++){ x = w * ((double)i - 0.5); sum = sum + 4.0/(1.0+x*x); } pi = w * sum; printf("Computed pi = %.16f\n",pi); 与Pthreads比较 2018年11月 共享存储编程

79 OpenMP结构化块类型 OpenMP主要有五类结构化块: 并行区Parallel Regions 任务分割Worksharing
数据环境Data Environment 同步Synchronization 运行时函数/环境变量 2018年11月 共享存储编程

80 OpenMP同步 共享变量读写 单一文件I/O OpenMP提共下列同步结构: – atomic – critical section
– barrier – flush – ordered – single – master 这两个结构实质上不属于同步结构,是并行区结构和任务分割结构中的内容. 2018年11月 共享存储编程

81 critical 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 2018年11月 共享存储编程

82 atomic Atomic是临界区的特例,主要用于某些简单的语句. #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 2018年11月 共享存储编程

83 下面两段代码区别是什么? 原子操作 临界区 #pragma omp parallel for \
shared(x, y, index, n) for (i=0; i<n; i++) { #pragma omp atomic x[index[i]] += work1(i); y[i] += work2(i); } #pragma omp parallel for \ shared(x, y, index, n) for (i=0; i<n; i++) { #pragma omp critical x[index[i]] += work1(i); y[i] += work2(i); } 原子操作 临界区 2018年11月 共享存储编程

84 原子操作和临界区比较 原子性是指操作的不可再分性,OpenMP利用原子结构主要是用于防止多线程对内存的同一地址的并发写.
临界区可以完成所有的原子操作. 原子结构可更好被编译优化: 有硬件指令可用于实现原子操作,而且系统开销也很小. 原子操作与并发并不矛盾, 临界区一定是串行执行的,原子操作不一定是串行执行 2018年11月 共享存储编程

85 barrier Barrier: 每一线程等待直至所有组内其它线程执行到Barrier为止. for结构有隐式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<N;i++) C[i]=big_calc3(I,A); #pragma omp for nowait B[i]=big_calc2(C, i); A[id] = big_calc3(id); } for结构有隐式barrier同步 for结构无隐式barrier同步 parallel结构隐式barrier同步 2018年11月 共享存储编程

86 ordered Ordered用于指定循环中各线程执行任务的顺序严格与顺序方式时的执行顺序一致.
如:在并行区中按顺序方式打印输出: void 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); 运行结果: 2018年11月 共享存储编程

87 master Master结构用于标志一个结构块只能由主线程执行. 其它线程跨越该块.该结构无隐式barrier同步.
#pragma omp parallel private (tmp) { do_many_things(); #pragma omp master { exchange_boundaries(); } #pragma barrier do_many_other_things(); } 2018年11月 共享存储编程

88 single Single结构用于标志一个块内的代码仅能由一个线程执行(不一定是主线程).
在single块后有一隐式的barrier同步操作. #pragma omp parallel{ #pragma omp single printf("Beginning work1.\n"); work1(); printf("Finishing work1.\n"); #pragma omp single nowait printf("Finished work1 and beginning work2.\n"); work2(); } 最早遇到single块者执行 同步 同步 不同步 2018年11月 共享存储编程

89 flush #pragma omp flush [(list)] new-line !$OMP FLUSH [(list)]
如:编译器必须将寄存器中的值写回内存,硬件刷新缓冲区等. 2018年11月 共享存储编程

90 Flush(Fortran) 隐式flush 下列语句无隐式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 2018年11月 共享存储编程

91 Flush(C) 隐式flush barrier Critical区的出入口 Ordered区的出入口 Parallel的出口 For的出口
Sections的出口 Single的出口 无隐式flush no wait 2018年11月 共享存储编程

92 隐式同步 下列OpenMP结构之后有隐式Barrier: end parallel end do (用nowait禁止)
end sections (用nowait禁止) end critical end single (用nowait禁止) 2018年11月 共享存储编程

93 Nesting 并行结构的嵌套 一个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的动态范围内. 2018年11月 共享存储编程

94 OpenMP结构化块类型 OpenMP主要有五类结构化块: 并行区Parallel Regions 任务分割Worksharing
数据环境Data Environment 同步Synchronization 运行时函数/环境变量 2018年11月 共享存储编程

95 OpenMP函数 锁函数 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() 2018年11月 共享存储编程

96 例:锁的使用 锁对象具有omp_lock_t,在锁使用前需对锁进行初始化: omp_init_lock().
下面的例子是用来显示锁的使用: 锁对象具有omp_lock_t,在锁使用前需对锁进行初始化: omp_init_lock(). 当一个线程试图获得锁以进入第一个临界区时,处理于空闲状态,以等待进入临界区. 一旦得到锁,则对临界区加锁: omp_set_lock(),退出完临界区时解锁omp_unset_lock(). 在进入第二个临界区时,则通过非阻塞函数omp_test_lock()来测试可能性,同时做一些其它的事情. 在锁不再使用时,用omp_destroy_lock()销毁. 2018年11月 共享存储编程

97 #include <omp.h> 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_destroy_lock(&lck); 2018年11月 共享存储编程

98 锁:保护共享资源 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); } 2018年11月 共享存储编程

99 设定动态模式,由程序来指定线程数(一般默认线程数与处理器个数相等).
#include <omp.h> 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); } 2018年11月 共享存储编程

100 指定线程数 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); } 2018年11月 共享存储编程

101 得到线程数 int numthreads; numthreads = omp_get_num_threads();
printf(“%d\n”,numthreads); #pragma omp parallel private(i) { } 运行: 1 4 2018年11月 共享存储编程

102 环境变量 线程任务调度控制 – 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) 2018年11月 共享存储编程

103 汇总 并行区 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 2018年11月 共享存储编程

104 omp_set_num_threads omp_get_num_threads omp_get_max_threads
运行环境函数汇总 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_nested 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_NESTED OMP_GET_WTIME OMP_GET_WTICK 2018年11月 共享存储编程

105 omp_destroy_nest_lock omp_set_lock omp_set_nest_lock omp_unset_lock
锁函数汇总 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_lock 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_LOCK 2018年11月 共享存储编程

106 环境变量(C和Fortran一样) OMP_SCHEDULE OMP_NUM_THREADS OMP_DYNAMIC OMP_NESTED
2018年11月 共享存储编程

107 OpenMP特点总结 支持C,C++和Fortran 共享存储并行机并行编程的主流平台,获得并行机厂商和用户的广泛支持
特别适合于串行程序的逐步并行化 如:先进行一些特别耗时的大循环的并行化,然后再逐步进行再大粒度的并行. Orphan制导语句的支持,使得OpenMP较之于以前的共享存储编程模型更适合粗粒度的并行 2018年11月 共享存储编程

108 谢谢! 2018年11月 共享存储编程


Download ppt "机群应用开发 并行编程原理及 程序设计 Parallel Programming: Fundamentals and Implementation 马少杰 mashj@sugon.com 曙光信息产业有限公司 2011.7."

Similar presentations


Ads by Google