机群应用开发 并行编程原理及 程序设计 Parallel Programming: Fundamentals and Implementation 曙光信息产业有限公司 2017年9月 2017年9月 MPI并行程序设计
参考文献 黄铠,徐志伟著,陆鑫达等译. 可扩展并行计算技术,结构与编程. 北京:机械工业出版社, 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. 2017年9月 MPI并行程序设计
相关网址 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 2017年9月 MPI并行程序设计
MPI并行程序设计 Parallel Programming with the Massage Passing Interface (MPI) 2017年9月 MPI并行程序设计
并行编程标准 多线程库标准 编译制导标准 消息传递库标准 – Win32 API. – POSIX threads. – OpenMP – 可移植共享存储并行编程标准. 消息传递库标准 – MPI – PVM 本讨论的重点 2017年9月 MPI并行程序设计
消息传递并行程序设计 消息传递并行程序设计 并行计算粒度大,特别适合于大规模可扩展并行算法 指用户必须通过显式地发送和接收消息来实现处理机间的数据交换。 在这种并行编程中,每个并行进程均有自己独立的地址空间,相互之间访问不能直接进行,必须通过显式的消息传递来实现。 这种编程方式是大规模并行处理机(MPP)和机群(Cluster)采用的主要编程方式。 并行计算粒度大,特别适合于大规模可扩展并行算法 由于消息传递程序设计要求用户很好地分解问题,组织不同进程间的数据交换,并行计算粒度大,特别适合于大规模可扩展并行算法. 消息传递是当前并行计算领域的一个非常重要的并行程序设计方式 2017年9月 MPI并行程序设计
什么是MPI? Massage Passing Interface:是消息传递函数库的标准规范,由MPI论坛开发,支持Fortran和C 2017年9月 MPI并行程序设计
MPI的发展过程 发展的两个阶段 MPI 1.1: 1995 MPICH:是MPI最流行的非专利实现,由Argonne国家实验室和密西西比州立大学联合开发,具有更好的可移植性. MPI 1.2~2.0:动态进程, 并行 I/O, 支持F90和C++(1997). 2017年9月 MPI并行程序设计
为什么要用MPI? 高可移植性 MPI已在IBM PC机上、MS Windows上、所有主要的Unix工作站上和所有主流的并行机上得到实现。使用MPI作消息传递的C或Fortran并行程序可不加改变地运行在IBM PC、MS Windows、Unix工作站、以及各种并行机上。 2017年9月 MPI并行程序设计
讲座内容提示 基本的MPI 深入MPI 实例 基本概念 点到点通信(Point to point) MPI程序的编译和运行 MPI中API的主要内容,为MPI最基本,最重要的内容 MPI程序的编译和运行 深入MPI 用户自定义(/派生)数据类型(User-defined(Derived) data type) 事实上MPI的所有数据类型均为MPI自定义类型 支持异构系统 允许消息来自不连续的或类型不一致的存储区(结构,数组散元) 集合通信(Collective) 数据移动,数据聚集,同步 基于point to point 构建 MPI环境管理函数 组,上下文和通信空间/通信子的管理 实例 2017年9月 MPI并行程序设计
从简单入手! 下面我们首先分别以C语言和Fortran语言的形式给出一个最简单的MPI并行程序Hello (下页). 该程序在终端打印出Hello World!字样. “Hello World”:一声来自新生儿的问候. 2017年9月 MPI并行程序设计
Hello world(C) #include <stdio.h> #include "mpi.h“ main( int argc, char *argv[] ) { MPI_Init( &argc, &argv ); printf( "Hello, world!\n" ); MPI_Finalize(); } 2017年9月 MPI并行程序设计
Hello world(Fortran) program main include ‘mpif.h’ integer ierr call MPI_INIT( ierr ) print *, 'Hello, world!' call MPI_FINALIZE( ierr ) end 2017年9月 MPI并行程序设计
C和Fortran中MPI函数约定 C Fortran MPI函数的参数被标志为以下三种类型: 必须包含mpi.h. MPI 函数返回出错代码或 MPI_SUCCESS成功标志. MPI-前缀,且只有MPI以及MPI_标志后的第一个字母大写,其余小写. Fortran 必须包含mpif.h. 通过子函数形式调用MPI,函数最后一个参数为返回值. MPI-前缀,且函数名全部为大写. MPI函数的参数被标志为以下三种类型: IN:参数在例程的调用中不会被修正. OUT:参数在例程的调用中可能会被修正. INOUT:参数在一些例程中为IN,而在另一些例程中为OUT. 2017年9月 MPI并行程序设计
MPI初始化-MPI_INIT int MPI_Init(int *argc, char **argv) MPI_INIT(IERROR) MPI_INIT是MPI程序的第一个调用,它完成MPI程序的所有初始化工作。所有的MPI程序的第一条可执行语句都是这条语句。 启动MPI环境,标志并行代码的开始. 并行代码之前,第一个mpi函数(除MPI_Initialize()外). 要求main必须带参数运行,否则出错. 2017年9月 MPI并行程序设计
MPI结束-MPI_FINALIZE int MPI_Finalize(void) MPI_FINALIZE(IERROR) MPI_FINALIZE是MPI程序的最后一个调用,它结束MPI程序的运行,它是MPI程序的最后一条可执行语句,否则程序的运行结果是不可预知的。 标志并行代码的结束,结束除主进程外其它进程. 之后串行代码仍可在主进程(rank = 0)上运行(如果必须). 2017年9月 MPI并行程序设计
MPI程序的的编译与运行 %小写o mpif77 hello.f 或 mpicc hello.c 默认生成a.out的可执行代码. mpif77 –o hello hello.f 或 mpicc –o hello hello.c 生成hello的可执行代码. mpirun –np 4 a.out mpirun –np 4 hello 4 指定np的实参,表示进程数,由用户指定. a.out / hello 要运行的MPI并行程序. %小写o np: The number of process. 2017年9月 MPI并行程序设计
:运行我们的MPI程序! [dair@node01 ~]$ mpicc -o hello hello.c [dair@node01 ~]$ ./hello () [0] Aborting program ! Could not create p4 procgroup. Possible missing fileor program started without mpirun. [dair@node01 ~]$ mpirun -np 4 hello () Hello World! [dair@node01 ~]$ 计算机打印字符 我们输入的命令 2017年9月 MPI并行程序设计
:Hello是如何被执行的? SPMD: Single Program Multiple Data(SPMD) :::: #include "mpi.h" #include <stdio.h> main( int argc, char *argv[] ) { MPI_Init( &argc, &argv ); printf( "Hello, world!\n" ); MPI_Finalize(); } #include "mpi.h" #include <stdio.h> main( int argc, char *argv[] ) { MPI_Init( &argc, &argv ); printf( "Hello, world!\n" ); MPI_Finalize(); } #include "mpi.h" #include <stdio.h> main( int argc, char *argv[] ) { MPI_Init( &argc, &argv ); printf( "Hello, world!\n" ); MPI_Finalize(); } #include "mpi.h" #include <stdio.h> main( int argc, char *argv[] ) { MPI_Init( &argc, &argv ); printf( "Hello, world!\n" ); MPI_Finalize(); } #include "mpi.h" #include <stdio.h> main( int argc, char *argv[] ) { MPI_Init( &argc, &argv ); printf( "Hello, world!\n" ); MPI_Finalize(); } Hello World! 2017年9月 MPI并行程序设计
:开始写MPI并行程序 在写MPI程序时,我们常需要知道以下两个问题的答案: 任务由多少个进程来进行并行计算? 我是哪一个进程? 2017年9月 MPI并行程序设计
:开始写MPI并行程序 MPI 提供了下列函数来回答这些问题: 用MPI_Comm_size 获得进程个数 p int MPI_Comm_size(MPI_Comm comm, int *size); 用MPI_Comm_rank 获得进程的一个叫rank的值,该 rank值为0到p-1间的整数,相当于进程的ID int MPI_Comm_rank(MPI_Comm comm, int *rank); 2017年9月 MPI并行程序设计
更新的Hello World(c) #include <stdio.h> #include "mpi.h" main( int argc, char *argv[] ) { int myid, numprocs; MPI_Init( &argc, &argv ); MPI_Comm_rank( MPI_COMM_WORLD, &myid ); MPI_Comm_size( MPI_COMM_WORLD, &numprocs ); printf(“I am %d of %d\n", myid, numprocs ); MPI_Finalize(); } 2017年9月 MPI并行程序设计
更新的Hello World(Fortran) program main include ‘mpif.h’ integer ierr, myid, numprocs call MPI_INIT( ierr ) call MPI_COMM_RANK( MPI_COMM_WORLD, myid, ierr ) call MPI_COMM_SIZE( MPI_COMM_WORLD, numprocs, ierr ) print *, ‘I am', myid, ‘of', numprocs call MPI_FINALIZE( ierr ) end 2017年9月 MPI并行程序设计
:运行结果 [dair@node01 ~]$ mpicc –o hello1 hello1.c [dair@node01 ~]$ mpirun -np 4 hello1 I am 0 of 4 I am 1 of 4 I am 2 of 4 I am 3 of 4 [dair@node01 ~]$ 计算机打印字符 我们输入的命令 2017年9月 MPI并行程序设计
有消息传递 Greeting 2017年9月 MPI并行程序设计
greetings(c) #include <stdio.h> #include "mpi.h" main(int argc, char* argv[]) { int numprocs, myid, source; MPI_Status status; char message[100]; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &myid); MPI_Comm_size(MPI_COMM_WORLD, &numprocs); 2017年9月 MPI并行程序设计
有消息传递greetings(c) if (myid != 0) { strcpy(message, "Hello World!"); MPI_Send(message,strlen(message)+1, MPI_CHAR, 0,99, MPI_COMM_WORLD); } else {/* myid == 0 */ for (source = 1; source < numprocs; source++) { MPI_Recv(message, 100, MPI_CHAR, source, 99, MPI_COMM_WORLD, &status); printf("%s\n", message); } MPI_Finalize(); } /* end main */ 2017年9月 MPI并行程序设计
解剖greetings程序 头文件: mpi.h/mpif.h. int MPI_Init(int *argc, char ***argv) 并行代码之前,第一个mpi函数(除MPI_Initialize()外). 要求main必须带能运行,否则出错. 通信子(通信空间): MPI_COMM_WORLD: 一个通信空间是一个进程组和一个上下文的组合.上下文可看作为组的超级标签,用于区分不同的通信子. 在执行函数MPI_Init之后,一个MPI程序的所有进程形成一个缺省的组,这个组的通信子即被写作MPI_COMM_WORLD. 该参数是MPI通信操作函数中必不可少的参数,用于限定参加通信的进程的范围. 2017年9月 MPI并行程序设计
解剖greetings程序 int MPI_Comm_size ( MPI_Comm comm, int *size ) 指定一个communicator,也指定了一组共享该空间的进程, 这些进程组成该communicator的group. int MPI_Comm_rank ( MPI_Comm comm, int *rank ) 得到本进程在通信空间中的rank值,即在组中的逻辑编号(从0开始). int MPI_Finalize() 标志并行代码的结束,结束除主进程外其它进程. 之后串行代码仍可在主进程(rank = 0)上运行(如果必须). 2017年9月 MPI并行程序设计
消息传送(先可不关心参数含义) 数据传送 + 同步操作 需要发送方与接收方合作完成. MPI_Send(A, 10, MPI_DOUBLE, 1,99, MPI_COMM_WORLD); MPI_Recv(B, 20, MPI_DOBULE, 0, 99, MPI_COMM_WORLD, &status); 数据传送 + 同步操作 Data Process 0 Process 1 发送请求 Yes Time 需要发送方与接收方合作完成. 2017年9月 MPI并行程序设计
最基本的MPI MPI调用借口的总数虽然庞大,但根据实际编写MPI的经验,常用的MPI调用的个数确实有限。下面是6个最基本的MPI函数。 MPI_Init(…); MPI_Comm_size(…); MPI_Comm_rank(…); MPI_Send(…); MPI_Recv(…); MPI_Finalize(); MPI_Init(…); … 并行代码; MPI_Fainalize(); 只能有串行代码; 2017年9月 MPI并行程序设计
讲座内容提示 基本的MPI 深入MPI 实例 基本概念 点到点通信(Point to point) MPI程序的编译和运行 MPI中API的主要内容,为MPI最基本,最重要的内容 MPI程序的编译和运行 深入MPI 用户自定义(/派生)数据类型(User-defined(Derived) data type) 事实上MPI的所有数据类型均为MPI自定义类型 支持异构系统 允许消息来自不连续的或类型不一致的存储区(结构,数组散元) 集合通信(Collective) 数据移动,数据聚集,同步 基于point to point 构建 MPI环境管理函数 组,上下文和通信空间/通信子的管理 实例 2017年9月 MPI并行程序设计
Point to Point 单个进程对单个进程的通信,重要且复杂 术语 Blocking(阻塞) :一个例程须等待操作完成才返回,返回后用户可以重新使用调用中所占用的资源. Non-blocking(非阻塞):一个例程不必等待操作完成便可返回,但这并不意味着所占用的资源可被重用. Local(本地):不依赖于其它进程. Non-local(非本地):依赖于其它进程. 2017年9月 MPI并行程序设计
Blocking Send int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm); IN buf 发送缓冲区的起始地址 IN count 要发送信息的元素个数 IN datatype 发送信息的数据类型 IN dest 目标进程的rank值 IN tag 消息标签 IN comm 通信子 2017年9月 MPI并行程序设计
Blocking Receive int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status); OUT buf 接收缓冲区的起始地址 IN count 要接收信息的元素个数 IN datatype 接收信息的数据类型 IN source 源进程的rank值 IN tag 消息标签 IN comm 通信子 OUT status status对象,包含实际接收到的消息的有关信息 2017年9月 MPI并行程序设计
MPI消息 MPI消息包括信封和数据两个部分,信封指出了发送或接收消息的对象及相关信息,而数据是本消息将要传递的内容 数据:<起始地址、数据个数、数据类型> 信封:<源/目的、标识、通信域> 2017年9月 MPI并行程序设计
消息数据 由count个类型为datatype的连续数据空间组成, 起始地址为buf 不是以字节数, 而是以元素的个数指定消息的长度 MPI基本数据类型相应于宿主语言的基本数据类型 2017年9月 MPI并行程序设计
什么是缓冲区? 应用程序中说明的变量,在消息传递语句中又用作缓冲区的起始位置. 也可表示由系统(不同用户)创建和管理的某一存储区域,在消息传递过程中用于暂存放消息.也被称为系统缓冲区. 用户可设置一定大小的存储区域,用作中间缓冲区以保留可能出现在其应用程序中的任意消息. 进程P A M 进程Q B 进程P A M T 进程Q B 用户缓冲区 进程P A M S 进程Q B 用户指定缓冲区 2017年9月 系统缓冲区 MPI并行程序设计
消息信封 MPI标识一条消息的信息包含四个域: Source: 发送进程隐式确定,由进程的rank值唯一标识 Destination: Send函数参数确定 Tag: Send函数参数确定,用于识别不同的消息 (0,UB),UB:MPI_TAG_UB>=32767. Communicator: 缺省MPI_COMM_WORLD Group:有限/N,有序/Rank [0,1,2,…N-1] Contex:Super_tag,用于标识该通讯空间. 2017年9月 MPI并行程序设计
2017年9月 MPI并行程序设计
消息匹配 接收buffer必须至少可以容纳count个由datatype参数指明类型的数据. 如果接收buf太小, 将导致溢出、出错. 参数匹配 dest,tag,comm/ source,tag,comm Source == MPI_ANY_SOURCE:接收任意处理器来的数据(任意消息来源). Tag == MPI_ANY_TAG:匹配任意tag值的消息(任意tag消息). 在阻塞式消息传送中不允许Source==Dest,否则会导致deadlock. 消息传送被限制在同一个communicator. 在send函数中必须指定唯一的接收者(Push/pull通讯机制). 2017年9月 MPI并行程序设计
status参数 当使用MPI_ANY_SOURCE或/和MPI_ANY_TAG接收消息时如何确定消息的来源source 和 tag值呢? 在C中,status.MPI_SOURCE, status.MPI_TAG. 在Fortran中, source=status(MPI_SOURCE), tag=status(MPI_TAG). Status还可用于返回实际接收到消息的长度 int MPI_Get_count(MPI_Status status, MPI_Datatype datatype,int* count) IN status 接收操作的返回值. IN datatype 接收缓冲区中元素的数据类型. OUT count 接收消息中的元素个数. 2017年9月 MPI并行程序设计
分析greetings #include <stdio.h> #include "mpi.h“ main(int argc, char* argv[]) { int numprocs; /*进程数,该变量为各处理器中的同名变量, 存储是分布的 */ int myid; /*我的进程ID,存储也是分布的 */ MPI_Status status; /*消息接收状态变量,存储也是分布的 */ char message[100]; /*消息buffer,存储也是分布的 */ /*初始化MPI*/ MPI_Init(&argc, &argv); /*该函数被各进程各调用一次,得到自己的进程rank值*/ MPI_Comm_rank(MPI_COMM_WORLD, &myid); /*该函数被各进程各调用一次,得到进程数*/ MPI_Comm_size(MPI_COMM_WORLD, &numprocs); 2017年9月 MPI并行程序设计
分析greetings if (myid != 0) { /*建立消息*/ sprintf(message, "Greetings from process %d!",myid); /* 发送长度取strlen(message)+1,使\0也一同发送出去*/ MPI_Send(message,strlen(message)+1, MPI_CHAR, 0,99,MPI_COMM_WORLD); } else { /* my_rank == 0 */ for (source = 1; source < numprocs; source++) MPI_Recv(message, 100, MPI_CHAR, source, 99, MPI_COMM_WORLD,&status); printf(“%s\n", message); /*关闭MPI,标志并行代码段的结束*/ MPI_Finalize(); } /* End main */ 2017年9月 MPI并行程序设计
? Greetings执行过程 % 假设进程数为3 问题:进程1和2谁先向根进程发送消息? (进程0) (进程1) (进程2) (进程0) (进程1) (进程2) (rank=0) (rank=1) (rank=2) . Recv(); . Send(); . Send() ? % 问题:进程1和2谁先向根进程发送消息? 2017年9月 MPI并行程序设计
运行greetings [dair@node01 ~]$ mpicc –o greeting greeting.c [dair@node01 ~]$ mpirun -np 4 greeting Greetings from process 1! Greetings from process 2! Greetings from process 3! [dair@node01 ~]$ 计算机打印字符 我们输入的命令 2017年9月 MPI并行程序设计
现在您已经能够用MPI进行并行编程了! 2017年9月 MPI并行程序设计
避免死锁deadlock 发送和接收是成对出现的,忽略这个原则 很可能会产生死锁 总会死锁的通信调用次序 2017年9月 MPI并行程序设计
不安全的通信调用次序 2017年9月 MPI并行程序设计
安全的通信调用次序 2017年9月 MPI并行程序设计
MPI_Sendrecv函数原型 int MPI_Sendrecv( p0 p1 P(n-1) 数据轮换 p2 pi void *sendbuf, int sendcount, MPI_Datatype sendtype, int dest, int sendtag, void *recvbuf, int recvcount, MPI_Datatype recvtype, int source, int recvtag, MPI_Comm comm, MPI_Status *status) 数据轮换 p0 p1 p2 P(n-1) pi 2017年9月 MPI并行程序设计
MPI_Sendrecv用法示意 该函数被每一进程执行一次. … int a,b; MPI_Status status; int dest = (rank+1)%p; int source = (rank + p -1)%p; /*p为进程个数*/ MPI_Sendrecv( &a, 1, MPI_INT, dest, 99, &b 1, MPI_INT, source, 99, MPI_COMM_WORLD, &status); 该函数被每一进程执行一次. 2017年9月 MPI并行程序设计
空进程 空进程 rank = MPI_PROC_NULL的进程称为空进程 p0 p1 p2 pi P(n-1) 使用空进程的通信不做任何操作. 向MPI_PROC_NULL发送的操作总是成功并立即返回. 从MPI_PROC_NULL接收的操作总是成功并立即返回,且接收缓冲区内容为随机数. status status.MPI_SOURCE = MPI_PROC_NULL status.MPI_TAG = MPI_ANY_TAG MPI_Get_count(&status,MPI_Datatype datatype, &count) =>count = 0 p0 p1 p2 P(n-1) pi 空进程 2017年9月 MPI并行程序设计
空进程应用示意 … MPI_Status status; int dest = (rank+1)%p; int source = (rank + p -1)%p; if(source == p-1) source = MPI_PROC_NULL; if(dest == 0) dest = MPI_PROC_NULL; MPI_Sendrecv( &a, 1, MPI_INT, dest, 99, &b 1, MPI_INT, source, 99, MPI_COMM_WORLD, &status); 2017年9月 MPI并行程序设计
阻塞与非阻塞的差别 用户发送缓冲区的重用: 阻塞发送将发生阻塞,直到通讯完成. 非阻塞可将通讯交由后台处理,通信与计算可重叠. 非阻塞的发送:仅当调用了有关结束该发送的语句后才能重用发送缓冲区,否则将导致错误;对于接收方,与此相同,仅当确认该接收请求已完成后才能使用。所以对于非阻塞操作,要先调用等待MPI_Wait()或测试MPI_Test()函数来结束或判断该请求,然后再向缓冲区中写入新内容或读取新内容。 阻塞发送将发生阻塞,直到通讯完成. 非阻塞可将通讯交由后台处理,通信与计算可重叠. 发送语句的前缀由MPI_改为MPI_I, I:immediate: 标准模式:MPI_Send(…)->MPI_Isend(…) Buffer模式:MPI_Bsend(…)->MPI_Ibsend(…) … 2017年9月 MPI并行程序设计
非阻塞发送与接收 int MPI_Isend(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) IN buf 发送缓冲区的起始地址 IN count 发送缓冲区的大小(发送元素个数) IN datatype 发送缓冲区数据的数据类型 IN dest 目的进程的秩 IN tag 消息标签 IN comm 通信空间/通信子 OUT request 非阻塞通信完成对象(句柄) int MPI_Irecv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request* request) 2017年9月 MPI并行程序设计
非阻塞标准发送和接收 2017年9月 MPI并行程序设计
通信的完成(常用于非阻塞通信) 发送的完成: 代表发送缓冲区中的数据已送出,发送缓冲区可以重用。它并不代表数据已被接收方接收。数据有可能被缓冲; 接收的完成:代表数据已经写入接收缓冲区。接收者可访问接收缓冲区。 通过MPI_Wait()和MPI_Test()来判断通信是否已经完成; 2017年9月 MPI并行程序设计
MPI_Wait()及应用示例 int MPI_Wait(MPI_Request* request, MPI_Status * status); 当request标识的通信结束后,MPI_Wait()才返回。如果通信是非阻塞的,返回时request = MPI_REQUEST_NULL;函数调用是非本地的; MPI_Request request; MPI_Status status; int x,y; if(rank == 0){ MPI_Isend(&x,1,MPI_INT,1,99,comm,&request) … MPI_Wait(&request,&status); }else{ MPI_Irecv(&y,1,MPI_INT,0,99,comm,&request) } 2017年9月 MPI并行程序设计
MPI_Test()及应用示例 //int MPI_Test(MPI_Request *request,int *flag, MPI_Status *status); MPI_Request request; MPI_Status status; int x,y,flag; if(rank == 0){ MPI_Isend(&x,1,MPI_INT,1,99,comm,&request) while(!flag) MPI_Test(&request,&flag,&status); }else{ MPI_Irecv(&y,1,MPI_INT,0,99,comm,&request) } 2017年9月 MPI并行程序设计
消息探测 --Probe函数(适用于阻塞与非阻塞) MPI_Probe()和MPI_Iprobe()函数探测接收消息的内容。用户根据探测到的消息内容决定如何接收这些消息,如根据消息大小分配缓冲区等。前者为阻塞方式,即只有探测到匹配的消息才返回;后者为非阻塞,即无论探测到与否均立即返回. int MPI_Probe(int source, int tag, MPI_Comm comm, MPI_Status* status) int MPI_Iprobe(int source, int tag, MPI_Comm comm, int*flag, MPI_Status* status) IN source 数据源的rank,可以是MPI_ANY_SOURCE IN tag 数据标签,可以是MPI_ANY_TAG IN comm 通信空间/通信子 OUT flag 布尔值,表示探测到与否(只用于非阻塞方式) OUT status status对象,包含探测到消息的内容 2017年9月 MPI并行程序设计
MPI_Probe应用示例 int x; float y; MPI_Comm_rank(comm, &rank); if(rank ==0) /*0->2发送一int型数*/ MPI_Send(100,1,MPI_INT,2,99,comm); else if(rank == 1) /*1->2发送一float型数*/ MPI_Send(100.0,1,MPI_FLOAT,2,99,comm); else /* 根进程接收 */ for(int i=0;i<2;i++) { MPI_Probe(MPI_ANY_SOURCE,0,comm,&status);/*Blocking*/ if (status.MPI_SOURCE == 0) MPI_Recv(&x,1,MPI_INT,0,99,&status); else if(status.MPI_SOURCE == 1) MPI_Recv(&y,1,MPI_FLOAT,0,99,&status); } 2017年9月 MPI并行程序设计
讲座内容提示 基本的MPI 深入MPI 实例 基本概念 点到点通信(Point to point) MPI程序的编译和运行 MPI中API的主要内容,为MPI最基本,最重要的内容 MPI程序的编译和运行 深入MPI 用户自定义(/派生)数据类型(User-defined(Derived) data type) 事实上MPI的所有数据类型均为MPI自定义类型 支持异构系统 允许消息来自不连续的或类型不一致的存储区(结构,数组散元) 集合通信(Collective) 数据移动,数据聚集,同步 基于point to point 构建 MPI环境管理函数 组,上下文和通信空间/通信子的管理 实例 2017年9月 MPI并行程序设计
MPI程序的编译 mpicc编译并连接用C语言编写的MPI程序 mpiCC编译并连接用C++编写的MPI程序 mpif77编译并连接用FORTRAN 77编写的MPI程序 mpif90编译并连接用Fortran 90编写的MPI程序 这些命令可以自动提供MPI需要的库,并提供特定的开关选项(用-help查看)。 2017年9月 MPI并行程序设计
MPI程序的编译 用mpicc编译时,就像用一般的C编译器一样。还可以使用一般的C的编译选项,含义和原来的编译器相同 例如: ./mpicc -c foo.c ./mpicc -o foo foo.o 2017年9月 MPI并行程序设计
MPI程序的运行 MPI程序的执行步骤一般为: 将可执行程序拷贝到各个节点机上 通过mpirun命令并行执行MPI程序 2017年9月 MPI并行程序设计
最简单的MPI运行命令 mpirun –np N <program> 其中: 例如: N: 同时运行的进程数 <program>: 可执行MPI程序名 例如: mpirun –np 6 cpi mpirun –np 4 hello 2017年9月 MPI并行程序设计
一种灵活的执行方式 mpirun –p4pg <pgfile> <program> <机器名> <进程数> <程序名> 例如: (注:第一行的0并不表示在node0上没有进程,这里的0特指在node0上启动MPI程序) node0 0 /public0/dair/mpi/cpi node1 1 /public0/dair/mpi/cpi node2 1 /public0/dair/mpi/cpi 这种方式允许可执行程序由不同的名字和不同的路径 2017年9月 MPI并行程序设计
另一种灵活的执行方式 mpirun –machinefile <machinefile> -np <N> <program> <machinefile>为配置文件,其格式为: <机器名> 例如: node0 node1 node2 node3 2017年9月 MPI并行程序设计
完整的MPI运行方式 MPI程序的一般启动方式: 完整的MPI运行方式: 详细参数信息执行mpirun -help mpirun –np <number of processor> <program name and argument> 完整的MPI运行方式: mpirun [mpirun_options] <program> [options…] 详细参数信息执行mpirun -help 2017年9月 MPI并行程序设计
讲座内容提示 基本的MPI 深入MPI 实例 基本概念 点到点通信(Point to point) MPI程序的编译和运行 MPI中API的主要内容,为MPI最基本,最重要的内容 MPI程序的编译和运行 深入MPI 用户自定义(/派生)数据类型(User-defined(Derived) data type) 事实上MPI的所有数据类型均为MPI自定义类型 支持异构系统 允许消息来自不连续的或类型不一致的存储区(结构,数组散元) 集合通信(Collective) 数据移动,数据聚集,同步 基于point to point 构建 MPI环境管理函数 组,上下文和通信空间/通信子的管理 实例 2017年9月 MPI并行程序设计
MPI数据类型 …… if (my_rank != 0) { /*建立消息*/ sprintf(message, "Greetings from process %d!",my_rank); /* 发送长度取strlen(message)+1,使\0也一同发送出去*/ MPI_Send(message,strlen(message)+1, MPI_CHAR, 0,99,MPI_COMM_WORLD); } else { /* my_rank == 0 */ for (source = 1; source < p; source++) MPI_Recv(message, 100, MPI_CHAR, source, 99, MPI_COMM_WORLD,&status); printf(“%s\n", message); /*关闭MPI,标志并行代码段的结束*/ MPI_Finalize(); } /* main */ 2017年9月 MPI并行程序设计
用户自定义数据类型/派生数据类型 目的 MPI中所有数据类型均为MPI自定义类型 派生数据类型:允许消息来自不连续的和类型不一致的存储区域,如数组散元与结构类型等的传送。 MPI中所有数据类型均为MPI自定义类型 基本数据类型,如MPI_INT,MPI_DOUBLE… 用户定义数据类型或派生数据类型. 2017年9月 MPI并行程序设计
MPI基本数据类型 2017年9月 MPI并行程序设计
数据类型图 2017年9月 MPI并行程序设计
Derived Datatype(派生) 常用 MPI_Type_contiguous MPI_Type_vector MPI_Type_indexed MPI_Type_struct 2017年9月 MPI并行程序设计
MPI_Type_contiguous 将原数据类型,按顺序进行多次复制 2017年9月 MPI并行程序设计
在FORTRAN中定义矩阵的一列 在C中定义矩阵的一行 REAL A(1000,1000) INTEGER F_COL MPI_TYPE_CONTIGUOUS(1000,MPI_REAL,F_COL) MPI_SEND(A,1,F_COL,right,tag,MPI_COMM_WORLD) 在C中定义矩阵的一行 float a[1000][1000]; MPI_Datatype C_R; MPI_Type_contiguous(1000,MPI_FLOAT,&C_R); MPI_SEND(&(a[0][0]),1,C_R, right,tag ,MPI_COMM_WORLD) 2017年9月 MPI并行程序设计
用MPI_Vector进行矩阵的行列置换 2017年9月 MPI并行程序设计
MPI_Vector函数原型 MPI_Vector()函数首先通过连续复制若干个旧数据类型形成一个“块”,然后通过等间隔地复制该块儿形成新的数据类型。块与块之间的空间时旧数据类型的倍数。 #include "mpi.h" int MPI_Type_vector ( int count, /*数据块个数 (非负整数)*/ int blocklen, /*块中元素个数 (非负整数)*/ int stride, /*块间起始地址间隔 (非负整数)*/ MPI_Datatype old_type, /*原始数据类型(句柄)*/ MPI_Datatype *newtype /*派生数据类型指针*/ ) 2017年9月 MPI并行程序设计
MPI_Type_vector应用示意 用MPI_Vector进行矩阵的行列置换 … float A[10][10]; MPI_Datatype column_mpi_t; MPI_Type_vector(10, 1, 10, MPI_FLOAT, &column_mpi_t); MPI_Type_commit(&column_mpi_t); if (my_rank == 0) MPI_Send(&(A[0][0]), 1, column_mpi_t, 1, 0, MPI_COMM_WORLD); else { /* my_rank = 1 */ MPI_Recv(&(A[0][0]), 10, MPI_FLOAT, 0, 0, MPI_COMM_WORLD, &status); 2017年9月 MPI并行程序设计
用MPI_Type_indexed发送矩阵的上三角部分 以C语言表示的数组为例,数组按行连续存储 2017年9月 MPI并行程序设计
MPI_Type_indexed函数原型 #include "mpi.h" int MPI_Type_indexed ( int count, /*数据块的个数,数据块间不连续*/ int blocklens[], /*每一数据块中元素的个数,为一个非负整型数组*/ int indices[], /*每一块数据在原始数据类型中的起始位置,整型数组*/ MPI_Datatype old_type, /*原始数据类型(名柄)*/ MPI_Datatype* newtype /*派生数据类型指针*/ ) 2017年9月 MPI并行程序设计
MPI_Type_indexed应用示意(将A矩阵的上三角部分送到另一个处理器中的T矩阵的对应位置) float A[n][n]; /* Complete Matrix */ float T[n][n]; /* Upper Triangle */ int displacements[n]; int block_lengths[n]; MPI_Datatype index_mpi_t; for (i = 0; i < n; i++) { block_lengths[i] = n-i; displacements[i] = (n+1)*i; } MPI_Type_indexed(n, block_lengths, displacements,MPI_FLOAT, &index_mpi_t); MPI_Type_commit(&index_mpi_t); if (my_rank == 0) MPI_Send(A, 1, index_mpi_t, 1, 0, MPI_COMM_WORLD); else /* my_rank == 1 */ MPI_Recv(T, 1, index_mpi_t, 0, 0, MPI_COMM_WORLD, &status); 2017年9月 MPI并行程序设计
MPI_Type_struct 允许每个块包含不同数据类型的拷贝 2017年9月 MPI并行程序设计
MPI_Type_struct的例子 struct partstruct {char class; double d[6]; char b[7]}; struct partstruct particle[1000]; int i,dest,rank; MPI_Datatype particletype,type[3]={MPI_CHAR, MPI_DOUBLE,MPI_CHAR} int blocklen[3]={1,6,7}; MPI_Aint disp[3]={0,sizeof(double),7*sizeof(double)}; MPI_Type_struct(3,blocklen,disp,type,&particletype); 2017年9月 MPI并行程序设计
其它派生类型 MPI_Hvector MPI_Hindexed MPI_Pack/MPI_Unpack:数据打包/解包 为了与早期其它并行库兼容 2017年9月 MPI并行程序设计
MPI_Pack () int MPI_Pack ( void *inbuf, /* 输入缓冲区起始地址*/ int incount, /* 输入数据项个数 */ MPI_Datatype datatype, /* 输入数据项的数据类型 */ void *outbuf, /* 输出缓冲区起始地址 */ int outcount, /* 输出缓冲区大小 */ int *position, /* 输出缓冲区当前位置 */ MPI_Comm comm /* 通信域 */ ) 例: packsize=0; MPI_Pack(&a,1,MPI_INT,packbuf,100,&packsize,MPI_COMM_WORLD); MPI_Pack(&b,1,MPI_DOUBLE, packbuf,100,&packsize,MPI_COMM_WORLD); 2017年9月 MPI并行程序设计
MPI_Unpack() int MPI_Unpack ( void *inbuf, /* 输入缓冲区起始地址*/ int incount, /* 输入数据项大小*/ int *position, /* 缓冲区当前位置 */ void *outbuf, /* 输出缓冲区起始地址 */ int outcount, /* 输出缓冲区大小 */ MPI_Datatype datatype, /* 输出数据项的数据类型 */ MPI_Comm comm /* 通信域 */ ) 例: pos=0; MPI_Unpack(packbuf,packsize,&pos,&a,1,MPI_INT,MPI_COMM_WROLD); MPI_Unpack(packbuf,packsize,&pos,&b,1,MPI_FLOAT,MPI_COMM_WROLD); 2017年9月 MPI并行程序设计
派生数据类型的应用 提交:int MPI_Type_commit(MPI Datatype *datatype) 将数据类型映射进行转换或“编译” 一种数据类型变量可反复定义,连续提交 释放:int MPI_Type free(MPI_Datatype *datatype) 将数据类型设为MPI_DATATYPE_NULL 2017年9月 MPI并行程序设计
讲座内容提示 基本的MPI 深入MPI 实例 基本概念 点到点通信(Point to point) MPI程序的编译和运行 MPI中API的主要内容,为MPI最基本,最重要的内容 MPI程序的编译和运行 深入MPI 用户自定义(/派生)数据类型(User-defined(Derived) data type) 事实上MPI的所有数据类型均为MPI自定义类型 支持异构系统 允许消息来自不连续的或类型不一致的存储区(结构,数组散元) 集合通信(Collective) 数据移动,数据聚集,同步 基于point to point 构建 MPI环境管理函数 组,上下文和通信空间/通信子的管理 实例 2017年9月 MPI并行程序设计
集合通信 Collective Communication 特点 通信空间中的所有进程都参与通信操作 每一个进程都需要调用该操作函数 一到多 多到一 同步 2017年9月 MPI并行程序设计
% MPI集合通信函数 All:表示结果到所有进程. V:Variety,被操作的数据对象和操作更为灵活. 类型 函数 功能 数据移动 MPI_Bcast 一到多,数据广播 MPI_Gather 多到一,数据汇合 MPI_Gatherv MPI_Gather的一般形式 MPI_Allgather MPI_Allgatherv MPI_Allgather的一般形式 MPI_Scatter 一到多,数据分散 MPI_Scatterv MPI_Scatter的一般形式 MPI_Alltoall 多到多,置换数据(全互换) MPI_Alltoallv MPI_Alltoall的一般形式 数据聚集 MPI_Reduce 多到一,数据归约 MPI_Allreduce 上者的一般形式,结果在所有进程 MPI_Reduce_scatter 结果scatter到各个进程 MPI_Scan 前缀操作 同步 MPI_Barrier 同步操作 MPI集合通信函数 2017年9月 MPI并行程序设计
数据移动 Broadcast Scatter Gather Allgather Alltoall 2017年9月 MPI并行程序设计
数据聚集 Reduce Allreduce Reduce-scatter Scan MPI 预定义全局数据运算符: MPI_MAX / MPI_MIN; MPI_SUM 求和 MPI_PROD 求积MPI_LAND 逻辑与 MPI_LOR 逻辑或 MPI_MAXLOC/MPI_MINLOC 最大/小值求下相应位置… … 2017年9月 MPI并行程序设计
Broadcast -- 数据广播 int p, myrank; float buf; MPI_Comm comm; MPI_Init(&argc, &argv); /*得进程编号*/ MPI_Comm_rank(comm, &my_rank); /* 得进程总数 */ MPI_Comm_size(comm, &p); if(myrank==0) buf = 1.0; MPI_Bcast(&buf,1,MPI_FLOAT,0, comm); data buf . MPI_Bcast(); Process 0 myrank = 0 Process 1 myrank = 1 Process p-1 myrank = p-1 int MPI_Bcast ( void *buffer,/*发送/接收buf*/ int count, /*元素个数*/ MPI_Datatype datatype, int root, /*指定根进程*/ MPI_Comm comm) 根进程既是发送缓冲区也是接收缓冲区 2017年9月 MPI并行程序设计
Gather -- 数据收集 int p, myrank; float data[10];/*分布变量*/ float* buf; MPI_Comm comm; MPI_Init(&argc, &argv); /*得进程编号*/ MPI_Comm_rank(comm,&my_rank); /* 得进程总数 */ MPI_Comm_size(comm, &p); if(myrank==0) buf=(float*)malloc(p*10*sizeof(float);/*开辟接收缓冲区*/ MPI_Gather(data,10,MPI_FLOAT, buf,10,MPI_FlOAT,0,comm); Process 0 myrank = 0 Process 1 myrank = 1 Process p-1 myrank = p-1 data . MPI_Gather(); data . MPI_Gather(); data . MPI_Gather(); buf 根进程接收其他进程来的消息(包括根进程),按每在进程在通信组中的编号依次联接在一下,存放在根进程的接收缓冲区中. int MPI_Gather ( void *sendbuf, int sendcnt, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm ) 2017年9月 MPI并行程序设计
Scatter -- 数据分散 int p, myrank; float data[10]; float* buf; MPI_Comm comm; MPI_Init(&argc, &argv); /*得进程编号*/ MPI_Comm_rank(comm,&my_rank); /* 得进程总数 */ MPI_Comm_size(comm, &p); if(myrank==0) buf = (float*)malloc(p*10*sizeof(float);/*开辟发送缓冲区*/ MPI_Scatter(buf,10,MPI_FLOAT, data,10,MPI_FlOAT,0,comm); Process 0 myrank = 0 Process 1 myrank = 1 Process p-1 myrank = p-1 data . MPI_Scatter(); data . MPI_ Scatter(); data . MPI_ Scatter(); buf 根进程中存储了p个消息,第i个消息将传给第i个进程. int MPI_Scatter ( void *sendbuf, int sendcnt, MPI_Datatype sendtype, void *recvbuf, int recvcnt, MPI_Datatype recvtype, int root, MPI_Comm comm ) 2017年9月 MPI并行程序设计
Reduce -- 全局数据运算 + int p, myrank; float data = 0.0; float buf; MPI_Comm comm; MPI_Init(&argc, &argv); /*得进程编号*/ MPI_Comm_rank(comm,&my_rank); /*各进程对data进行不同的操作*/ data = data + myrank * 10; /*将各进程中的data数相加并存入根进程的buf中 */ MPI_Reduce(&data,&buf,1,MPI_FLOAT,MPI_SUM,0,comm); Process 0 myrank = 0 Process 1 myrank = 1 Process p-1 myrank = p-1 data . MPI_Scatter(); data . MPI_ Scatter(); data . MPI_ Scatter(); buf + 对组中所有进程的发送缓冲区中的数据用OP参数指定的操作进行运算,并将结果送回到根进程的接收缓冲区中. int MPI_Reduce ( void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm ) 2017年9月 MPI并行程序设计
集合通信的应用 k=m/p p0 p0 p0 p1 p2 p3 p1 p1 p2 p2 p3 p3 集合通信在向量-矩阵乘中的应用 向量按行存储 集合通信在向量-矩阵乘中的应用 2017年9月 MPI并行程序设计
后缀V:更灵活的集合通信 带后缀V的集合通信操作是一种更为灵活的集合通信操作 通信中元素块的大小可以变化 发送与接收时的数据位置可以不连续 2017年9月 MPI并行程序设计
MPI_Gather int MPI_Gather ( void *sendbuf, int sendcnt, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm ) 参数: sendbuf 发送缓冲区起始位置 sendcount 发送元素个数 sendtype 发送数据类型 recvcount 接收元素个数(所有进程相同) (该参数仅对根进程有效) recvtype 接收数据类型(仅在根进程中有效) root 通过rank值指明接收进程 comm 通信空间 2017年9月 MPI并行程序设计
MPI_Gatherv int MPI_Gatherv ( void *sendbuf, int sendcnt, MPI_Datatype sendtype, void *recvbuf, int *recvcnts, int *displs, MPI_Datatype recvtype, int root, MPI_Comm comm ) 参数: sendbuf 发送缓冲区的起始位置 sendcount 发送元素个数 sendtype 发送数据类型 recvcounts 整型数组(大小等于组的大小),用于指明从各进程要接收的元素的个数(仅对根进程有效) displs 整型数组(大小等于组的大小). 其元素 i指明要接收元素存放位置相对于接收缓冲区起始位置的偏移量 (仅在根进程中有效) recvtype 接收数据类型 root 通过rank值指明接收进程 comm 通信空间 2017年9月 MPI并行程序设计
Gather与GatherV Gather GatherV GatherV 应用Vector派生数据类型 2017年9月 MPI并行程序设计
Scatter与ScatterV Scatter 应用Vector派生数据类型 ScatterV ScatterV 2017年9月 MPI并行程序设计
注意事项 组内所有进程都参与才能完成 各进程的调用形式都相同 常见问题 有的调用,有的不调用(主进程广播,从进程没有广播) 广播语句和接收语句对应 调用参数不对(若使用一对多或者多对一通信,则各个进程所使用的ROOT值必须都是相同的) 2017年9月 MPI并行程序设计
讲座内容提示 基本的MPI 深入MPI 实例 基本概念 点到点通信(Point to point) MPI程序的编译和运行 MPI中API的主要内容,为MPI最基本,最重要的内容 MPI程序的编译和运行 深入MPI 用户自定义(/派生)数据类型(User-defined(Derived) data type) 事实上MPI的所有数据类型均为MPI自定义类型 支持异构系统 允许消息来自不连续的或类型不一致的存储区(结构,数组散元) 集合通信(Collective) 数据移动,数据聚集,同步 基于point to point 构建 MPI环境管理函数 组,上下文和通信空间/通信子的管理 实例 2017年9月 MPI并行程序设计
MPI环境管理 MPI起动与结束: MPI计时函数 组,上下文和通信空间管理. MPI_Init(); MPI_Initialized();测试是否已执行MPI_Init(); MPI_Finalize(); MPI计时函数 double MPI_Wtime();返回自过去某一时刻调用时的时间间隔,以秒为单位. double MPI_Wtick();返回用作硬件计时的两次脉冲间的间隔时间,以秒为单位. 组,上下文和通信空间管理. 2017年9月 MPI并行程序设计
通信域 通信域:描述进程间的通信关系,包括 通信上下文:区别不同的通信域,一个上下文所发送的消息不能被另一个上下文所接收 进程组:多个进程的有序集合 虚拟拓扑:多个进程在逻辑上的排列关系,反映了进程间的通信模型 属性:用户可通过自定义的属性,将任意信息附加到通信域上 2017年9月 MPI并行程序设计
预定义的进程组和通信域 MPI_GROUP_NULL MPI_COMM_NULL MPI_GROUP_EMPTY MPI_COMM_SELF 无效进程组句柄 MPI_COMM_NULL 无效通信域句柄 MPI_GROUP_EMPTY 有效进程组句柄,包括元素个数为0 MPI_COMM_SELF 有效通信域句柄,包括元素仅为当前进程 MPI_COMM_WORLD 有效通信域句柄,包括元素为所有进程 2017年9月 MPI并行程序设计
进程组 不同的进程可以有不同的分工,可为之建立不同的通信域,这要借助进程组完成。 得到通信域对应的进程组: 根据组创建新的通信域: MPI_COMM_GROUP(comm,group) 根据组创建新的通信域: MPI_COMM_CREATE(comm,g,ncomm) 2017年9月 MPI并行程序设计
进程组操作 比较: 并: 交: 差: MPI_GROUP_COMPARE(g1,g2,result) MPI_GROUP_UNION(g1,g2,newg) 交: MPI_GROUP_INTERSECTION(g1,g2,newg) 差: MPI_GROUP_DIFFERENCE(g1,g2,newg) 2017年9月 MPI并行程序设计
所包含的进程形成新组: 去除掉进程后形成新组: 得到组的大小 得到当前进程在组中的编号 MPI_GROUP_INCL(g,n,ranks,newg) 去除掉进程后形成新组: MPI_GROUP_EXCL(g,n,ranks,newg) 得到组的大小 MPI_GROUP_SIZE(group,size) 得到当前进程在组中的编号 MPI_GROUP_RANK(group,rank) 2017年9月 MPI并行程序设计
进程组的例子 2017年9月 MPI并行程序设计
通信域 比较 复制 释放 MPI_COMM_COMPARE(comm1,comm2,result) MPI_COMM_DUP(comm,newcomm) 释放 MPI_COMM_FREE(comm) 2017年9月 MPI并行程序设计
通信域的分裂MPI_COMM_SPLIT(comm,color,key,newcomm) 2017年9月 MPI并行程序设计
通信域分裂的例子 2017年9月 MPI并行程序设计
虚拟拓扑 在许多并行应用程序中,进程的线性排列不能充分地反映进程间的通信模型 进程经常被排列成二维或三维网格形式的拓扑模型,而且通常用一个图来描述逻辑进程排列 不同的进程拓扑结构,可使程序设计更为自然,也为在相近的物理拓扑上的高效实现提供支持。 2017年9月 MPI并行程序设计
图拓扑 节点表示进程 边表示进程之间的通信 可以表示所有类型的通信 2017年9月 MPI并行程序设计
笛卡尔拓扑 使用环、网格等进程拓扑时,笛卡尔坐标更为方便 2017年9月 MPI并行程序设计
查看topo类型 Status:MPI_GRAPH、MPI_CART、MPI_UNDEFINED 2017年9月 MPI并行程序设计
创建笛卡儿拓扑 2*7 2017年9月 MPI并行程序设计
调用 MPI_COMM_WORLD,2,{4,5},{false,false},false,newcomm 2017年9月
得到每一维的大小 MPI_DIMS_CREATE(nnodes, ndims,dims) (20,2,{4,5}) 2017年9月 MPI并行程序设计
得到卡氏坐标 MPI_CART_COORDS(comm, rank, maxdims, coords) 得到rank值对应的笛卡尔坐标 2017年9月 MPI并行程序设计
平移操作 MPI_CART_SHIFT(comm, direction, disp, rank_source, rank_dest) 哪一维 偏移 当前进程 向右偏移3 2017年9月 MPI并行程序设计
例子 2017年9月 MPI并行程序设计
例子(续) 2017年9月 MPI并行程序设计
创建图拓扑 2017年9月 MPI并行程序设计
2017年9月 MPI并行程序设计
得到相邻进程数 得到与指定进程rank相邻的进程总数nneighbors 2017年9月 MPI并行程序设计
得到相邻进程标识 得到与指定进程rank相邻的进程标识neighbors 2017年9月 MPI并行程序设计
讲座内容提示 基本的MPI 深入MPI 实例 基本概念 点到点通信(Point to point) MPI程序的编译和运行 MPI中API的主要内容,为MPI最基本,最重要的内容 MPI程序的编译和运行 深入MPI 用户自定义(/派生)数据类型(User-defined(Derived) data type) 事实上MPI的所有数据类型均为MPI自定义类型 支持异构系统 允许消息来自不连续的或类型不一致的存储区(结构,数组散元) 集合通信(Collective) 数据移动,数据聚集,同步 基于point to point 构建 MPI环境管理函数 组,上下文和通信空间/通信子的管理 实例 2017年9月 MPI并行程序设计
实例分析 求PI 向量点积 矩阵向量相乘 Jacobi迭代 并行拉格朗日元算法 2017年9月 MPI并行程序设计
实例分析:求PI 2017年9月 MPI并行程序设计
串行代码 h=1.0/(double)n; sum=0.0; for (i=1; i<=n; i++) { x=h*((double)i – 0.5); sum += f(x); } pi=h*sum; double f(double a) { return (4.0/(1.0+a*a)); } 2017年9月 MPI并行程序设计
并行代码 double f(double a) h=1.0/(double)n; { sum=0.0; return (4.0/(1.0+a*a)); } h=1.0/(double)n; sum=0.0; for (i=myid+1; i<=n; i+=numprocs) { x=h*((double)i – 0.5); sum += f(x); } mypi=h*sum; MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); 2017年9月 MPI并行程序设计
cpi.c #include "mpi.h" #include <stdio.h> #include <math.h> double f( double ); double f( double a ) { return (4.0 / (1.0 + a*a)); } 2017年9月 MPI并行程序设计
cpi.c int main( int argc, char *argv[]) { int done = 0, n, myid, numprocs, i; double PI25DT = 3.141592653589793238462643; double mypi, pi, h, sum, x; double startwtime = 0.0, endwtime; int namelen; char processor_name[MPI_MAX_PROCESSOR_NAME]; MPI_Init(&argc,&argv); MPI_Comm_size(MPI_COMM_WORLD,&numprocs); MPI_Comm_rank(MPI_COMM_WORLD,&myid); MPI_Get_processor_name(processor_name,&namelen); fprintf(stderr,"Process %d on %s\n", myid, processor_name); 2017年9月 MPI并行程序设计
cpi.c n = 100; while (!done) { if (myid == 0) startwtime = MPI_Wtime(); } MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD); 2017年9月 MPI并行程序设计
cpi.c if (n == 0) done = 1; else { h = 1.0 / (double) n; sum = 0.0; for (i = myid + 1; i <= n; i += numprocs) { x = h * ((double)i - 0.5); sum += f(x); } mypi = h * sum; 2017年9月 MPI并行程序设计
cpi.c MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); if (myid == 0) { printf("pi is approximately %.16f, Error is %.16f\n", pi, fabs(pi - PI25DT)); endwtime = MPI_Wtime(); printf("wall clock time = %f\n", endwtime-startwtime); } MPI_Finalize(); return 0; 2017年9月 MPI并行程序设计
代码导读 main() pi:圆周率的计算结果 mypi:每个进程的积分结果 n:积分区间数量 h:积分区间大小 2017年9月 MPI并行程序设计
实例分析:点积运算 p0 p0 p1 p2 2017年9月 MPI并行程序设计
Parallel_dot.c /* parallel_dot.c -- compute a dot product of a * vector distributed among the processes. * Uses a block distribution of the vectors. * Input: * n: global order of vectors * x, y: the vectors * Output: * the dot product of x and y. * * Note: Arrays containing vectors are statically allocated. Assumes * n, the global order of the vectors, is divisible by p, the number * of processes. */ 2017年9月 MPI并行程序设计
#include <stdio.h> #include "mpi.h" #define MAX_LOCAL_ORDER 100 main(int argc, char* argv[]) { float local_x[MAX_LOCAL_ORDER]; float local_y[MAX_LOCAL_ORDER]; int n; int n_bar; /* = n/p */ float dot; int p; int my_rank; void Read_vector(char* prompt, float local_v[], int n_bar, int p, int my_rank); float Parallel_dot(float local_x[], float local_y[], int n_bar); 2017年9月 MPI并行程序设计
MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &p); MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); if (my_rank == 0) { printf("Enter the order of the vectors\n"); scanf("%d", &n); } MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD); n_bar = n/p; Read_vector("the first vector", local_x, n_bar, p, my_rank); Read_vector("the second vector", local_y, n_bar, p, my_rank); dot = Parallel_dot(local_x, local_y, n_bar); if (my_rank == 0) printf("The dot product is %f\n", dot); MPI_Finalize(); } /* main */ 2017年9月 MPI并行程序设计
void Read_vector(char* prompt/* in */,float local_v[]/* out */, int n_bar/* in */,int p/* in */, int my_rank/* in */) { int i, q; float temp[MAX_LOCAL_ORDER]; MPI_Status status; if (my_rank == 0) { printf("Enter %s\n", prompt); for (i = 0; i < n_bar; i++) scanf("%f", &local_v[i]); for (q = 1; q < p; q++) { scanf("%f", &temp[i]); MPI_Send(temp, n_bar, MPI_FLOAT, q, 0, MPI_COMM_WORLD); } } else { MPI_Recv(local_v, n_bar, MPI_FLOAT, 0, 0, MPI_COMM_WORLD, &status); } /* Read_vector */ 2017年9月 MPI并行程序设计
float Serial_dot( float x[] /* in */, float y[] /* in */, int n /* in */) { int i; float sum = 0.0; for (i = 0; i < n; i++) sum = sum + x[i]*y[i]; return sum; } /* Serial_dot */ 2017年9月 MPI并行程序设计
float local_x[] /* in */, float local_y[] /* in */, float Parallel_dot( float local_x[] /* in */, float local_y[] /* in */, int n_bar /* in */) { float local_dot; float dot = 0.0; float Serial_dot(float x[], float y[], int m); local_dot = Serial_dot(local_x, local_y, n_bar); MPI_Reduce(&local_dot, &dot, 1, MPI_FLOAT, MPI_SUM, 0, MPI_COMM_WORLD); return dot; } /* Parallel_dot */ 2017年9月 MPI并行程序设计
代码导读 main() Read_vector() Serial_dot() Parallel_dot() local_x / local_y:本地计算的矢量分量 n:矢量的全局阶数 n_bar:本地计算的分量阶数 dot:点积结果 Read_vector() temp:每个进程计算的矢量分量 Serial_dot() Parallel_dot() local_dot:本地的计算结果 2017年9月 MPI并行程序设计
实例分析:矩阵向量相乘 p0 p1 p2 2017年9月 MPI并行程序设计
parallel_mat_vect.c #include <stdio.h> #include "mpi.h" #define MAX_ORDER 100 typedef float LOCAL_MATRIX_T[MAX_ORDER][MAX_ORDER]; main(int argc, char* argv[]) { int my_rank; int p; LOCAL_MATRIX_T local_A; float global_x[MAX_ORDER]; float local_x[MAX_ORDER]; float local_y[MAX_ORDER]; int m, n; int local_m, local_n; 2017年9月 MPI并行程序设计
void Read_matrix(char* prompt, LOCAL_MATRIX_T local_A, int local_m, int n, int my_rank, int p); void Read_vector(char* prompt, float local_x[], int local_n, int my_rank, int p); void Parallel_matrix_vector_prod( LOCAL_MATRIX_T local_A, int m, int n, float local_x[], float global_x[], float local_y[], int local_m, int local_n); void Print_matrix(char* title, LOCAL_MATRIX_T local_A, int local_m, void Print_vector(char* title, float local_y[], int local_m, int my_rank, MPI_Init(&argc, &argv); MPI_Comm_size(MPI_COMM_WORLD, &p); MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); if (my_rank == 0) { printf("Enter the order of the matrix (m x n)\n"); scanf("%d %d", &m, &n); } MPI_Bcast(&m, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD); 2017年9月 MPI并行程序设计
Read_matrix("Enter the matrix", local_A, local_m, n, my_rank, p); local_m = m/p; local_n = n/p; Read_matrix("Enter the matrix", local_A, local_m, n, my_rank, p); Print_matrix("We read", local_A, local_m, n, my_rank, p); Read_vector("Enter the vector", local_x, local_n, my_rank, p); Print_vector("We read", local_x, local_n, my_rank, p); Parallel_matrix_vector_prod(local_A, m, n, local_x, global_x, local_y, local_m, local_n); Print_vector("The product is", local_y, local_m, my_rank, p); MPI_Finalize(); } /* main */ 2017年9月 MPI并行程序设计
void Read_matrix(char* prompt /* in */, LOCAL_MATRIX_T local_A /* out */, int local_m /* in */, int n /* in */, int my_rank /* in */, int p /* in */) { int i, j; LOCAL_MATRIX_T temp; /* Fill dummy entries in temp with zeroes */ for (i = 0; i < p*local_m; i++) for (j = n; j < MAX_ORDER; j++) temp[i][j] = 0.0; if (my_rank == 0) { printf("%s\n", prompt); for (j = 0; j < n; j++) scanf("%f",&temp[i][j]); } MPI_Scatter(temp, local_m*MAX_ORDER, MPI_FLOAT, local_A, local_m*MAX_ORDER, MPI_FLOAT, 0, MPI_COMM_WORLD); } /* Read_matrix */ 2017年9月 MPI并行程序设计
float local_x[] /* out */, int local_n /* in */, int my_rank /* in */, void Read_vector( char* prompt /* in */, float local_x[] /* out */, int local_n /* in */, int my_rank /* in */, int p /* in */) { int i; float temp[MAX_ORDER]; if (my_rank == 0) { printf("%s\n", prompt); for (i = 0; i < p*local_n; i++) scanf("%f", &temp[i]); } MPI_Scatter(temp, local_n, MPI_FLOAT, local_x, local_n, MPI_FLOAT, 0, MPI_COMM_WORLD); } /* Read_vector */ 2017年9月 MPI并行程序设计
/* All arrays are allocated in calling program */ /* Note that argument m is unused */ void Parallel_matrix_vector_prod( LOCAL_MATRIX_T local_A /* in */, int m /* in */, int n /* in */, float local_x[] /* in */, float global_x[] /* in */, float local_y[] /* out */, int local_m /* in */, int local_n /* in */) { /* local_m = m/p, local_n = n/p */ int i, j; MPI_Allgather(local_x, local_n, MPI_FLOAT, global_x, local_n, MPI_FLOAT, MPI_COMM_WORLD); for (i = 0; i < local_m; i++) { local_y[i] = 0.0; for (j = 0; j < n; j++) local_y[i] = local_y[i] + local_A[i][j]*global_x[j]; } } /* Parallel_matrix_vector_prod */ 2017年9月 MPI并行程序设计
LOCAL_MATRIX_T local_A /* in */, int local_m /* in */, int n /* in */, void Print_matrix( char* title /* in */, LOCAL_MATRIX_T local_A /* in */, int local_m /* in */, int n /* in */, int my_rank /* in */, int p /* in */) { int i, j; float temp[MAX_ORDER][MAX_ORDER]; MPI_Gather(local_A, local_m*MAX_ORDER, MPI_FLOAT, temp, local_m*MAX_ORDER, MPI_FLOAT, 0, MPI_COMM_WORLD); if (my_rank == 0) { printf("%s\n", title); for (i = 0; i < p*local_m; i++) { for (j = 0; j < n; j++) printf("%4.1f ", temp[i][j]); printf("\n"); } } /* Print_matrix */ 2017年9月 MPI并行程序设计
float local_y[] /* in */, int local_m /* in */, int my_rank /* in */, void Print_vector( char* title /* in */, float local_y[] /* in */, int local_m /* in */, int my_rank /* in */, int p /* in */) { int i; float temp[MAX_ORDER]; MPI_Gather(local_y, local_m, MPI_FLOAT, temp, local_m, MPI_FLOAT, 0, MPI_COMM_WORLD); if (my_rank == 0) { printf("%s\n", title); for (i = 0; i < p*local_m; i++) printf("%4.1f ", temp[i]); printf("\n"); } } /* Print_vector */ 2017年9月 MPI并行程序设计
代码导读 main() Read_matrix() Read_vector() Parallel_matrix_vector_prod() local_A:本地矩阵 global_x:全局矢量 local_x:本地输入矢量 local_y:本地结果矢量 Read_matrix() temp:全局矩阵 Read_vector() temp:全局输入矢量 Parallel_matrix_vector_prod() Print_matrix() Print_vector() 2017年9月 MPI并行程序设计
实例分析:Jacobi迭代 最基本的Jacobi迭代 SENDRECV实现 引入MPI_PROC_NULL 定义数据类型 定义进程拓扑 2017年9月 MPI并行程序设计
拉普拉斯方程 求解拉普拉斯方程是电磁学、天文学和流体力学等领域经常遇到的一类重要的数学问题,因为这种方程以势函数的形式描写了电场、引力场和流场等物理对象(一般统称为“保守场”或“有势场”)的性质。 2017年9月 MPI并行程序设计
Jacobi迭代 2017年9月 MPI并行程序设计
基本含义 上下左右相加取平均 使用旧值 2017年9月 MPI并行程序设计
数据划分 按行划分 处理器个数 2017年9月 MPI并行程序设计
各个进程空间的声明 上下都多一行 大小(N,N/NP),假设正好整除 2017年9月 MPI并行程序设计
任取一个进程的执行轨迹 1 得到边界数据 2 循环计算新值 3 更新旧值 2017年9月 MPI并行程序设计
应该采取的模式 1 接收边界数据同时提供边界数据 2 循环计算得到新值 3 更新旧值 2017年9月 MPI并行程序设计
具体的例子 数组大小100×100 处理器个数4 2017年9月 MPI并行程序设计
局部数组大小 100/4=25,划分后的行 25×100,本地数组大小 27×100,加上边界后数组的大小 2017年9月 MPI并行程序设计
通信得到或者发出边界数据 向上平移,上面的进程接收,下面的进程发送 2017年9月 MPI并行程序设计
平移前后的对比 平移前 平移后 2017年9月 MPI并行程序设计
通信语句 if (不是最下一个) 从下面接收边界数据MPI_RECV if (不是最上面一个),向上面发送边界数据 MPI_SEND 2017年9月 MPI并行程序设计
通信得到或者发出边界数据 向下平移,上面的发送,下面的接收 2017年9月 MPI并行程序设计
平移前后的对比 平移前 平移后 2017年9月 MPI并行程序设计
通信语句 if (不是最上面一个) 从上面接收边界数据MPI_RECV if (不是最下面一个),向下面发送边界数据 MPI_SEND 2017年9月 MPI并行程序设计
计算部分 循环从第?行开始,到倒数第?行结束执行迭代过程 更新数组 2017年9月 MPI并行程序设计
程序 #include "mpi.h" #define arysize 100 #define myarysize arysize/4 int main(int argc, char *argv[]) { int n, myid, numprocs, i, j, nsteps=10; float a[myarysize+2][arysize],b[myarysize+2][arysize]; int begin_row,end_row; MPI_Status status; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&myid); 2017年9月 MPI并行程序设计
if (myid == 0) begin_row=2; if (myid == 3) end_row=myarysize-1; for (n=0; n<nsteps; n++) { if (myid<3) { MPI_Recv(&a[myarysize+1][0],100,MPI_FLOAT, myid+1,1000,MPI_COMM_WORLD,&status); MPI_Send(&a[myarysize][0],100,MPI_FLOAT, myid+1,1000,MPI_COMM_WORLD); } if (myid>0) { MPI_Send(&a[1][0],100,MPI_FLOAT, myid-1,1000,MPI_COMM_WORLD); MPI_Recv(&a[0][0],100,MPI_FLOAT, myid-1,1000,MPI_COMM_WORLD,&status); 2017年9月 MPI并行程序设计
for (i=begin_row;i<=end_row;i++ ) for ( j=1;j<arysize-1;j++) b[i][j] = (a[i][j+1]+a[i][j-1]+a[i-1][j]+a[i+1][j])*0.25; a[i][j] = b[i][j]; } MPI_Finalize(); 2017年9月 MPI并行程序设计
问题 6 5 容易产生死锁 4 3 2 1 2017年9月 MPI并行程序设计
办法 采用MPI_SENDRECV语句和MPI_PROCE_NULL 2017年9月 MPI并行程序设计
用SENDRECV来重写 #include "mpi.h" #define arysize 100 #define myarysize arysize/4 int main(int argc, char *argv[]) { int n,myid, numprocs, i, j, nsteps=10; int up,down; float a[myarysize+2][arysize],b[myarysize+2][arysize]; int begin_row,end_row; MPI_Status status; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&myid); 2017年9月 MPI并行程序设计
if (up<0) up=MPI_PROC_NULL; down=myid+1; begin_row=1; end_row=myarysize; up=myid-1; if (up<0) up=MPI_PROC_NULL; down=myid+1; if (down>3) down=MPI_PROC_NULL; if (myid == 0) begin_row=2; if (myid == 3) end_row=myarysize-1; for (n=0; n<nsteps; n++) { MPI_Sendrecv(&a[1][0],100,MPI_FLOAT,up,1000,&a[myarysize+1][0],100,MPI_FLOAT,down,1000,MPI_COMM_WORLD,&status); MPI_Sendrecv(&a[myarysize][0],100,MPI_FLOAT,down,1000,&a[0][0],100,MPI_FLOAT,up,1000,MPI_COMM_WORLD,&status); 2017年9月 MPI并行程序设计
for (i=begin_row;i<=end_row;i++ ) for ( j=1;j<arysize-1;j++) b[i][j] = (a[i][j+1]+a[i][j-1]+a[i-1][j]+a[i+1][j])*0.25; a[i][j] = b[i][j]; } MPI_Finalize(); 2017年9月 MPI并行程序设计
定义新的数据类型 定义一行 MPI_CONTIGUOUS 2017年9月 MPI并行程序设计
程序 #include "mpi.h" #define arysize 100 #define myarysize arysize/4 int main(int argc, char *argv[]) { int n,myid, numprocs, i, j, nsteps=10; int up,down; float a[myarysize+2][arysize],b[myarysize+2][arysize]; int begin_row,end_row; MPI_Datatype onerow; MPI_Status status; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&myid); MPI_Type_contiguous(100,MPI_FLOAT,&onerow); 2017年9月 MPI并行程序设计
MPI_Type_commit( &onerow ); begin_row=1; end_row=myarysize; up=myid-1; if (up<0) up=MPI_PROC_NULL; down=myid+1; if (down>3) down=MPI_PROC_NULL; if (myid == 0) begin_row=2; if (myid == 3) end_row=myarysize-1; for (n=0; n<nsteps; n++) { MPI_Sendrecv(&a[1][0],1,onerow,up,1000,&a[myarysize+1][0],1,onerow,down,1000,MPI_COMM_WORLD,&status); MPI_Sendrecv(&a[myarysize][0],1,onerow,down,1000,&a[0][0],1,onerow,up,1000,MPI_COMM_WORLD,&status); 2017年9月 MPI并行程序设计
for (i=begin_row;i<=end_row;i++ ) for ( j=1;j<arysize-1;j++) b[i][j] = (a[i][j+1]+a[i][j-1]+a[i-1][j]+a[i+1][j])*0.25; a[i][j] = b[i][j]; } MPI_Type_free( &onerow ); MPI_Finalize(); 2017年9月 MPI并行程序设计
定义虚拟进程拓扑 块状分布 2017年9月 MPI并行程序设计
#define arysize2 (arysize/2) int main(int argc, char *argv[]) { #include "mpi.h" #define arysize 256 #define arysize2 (arysize/2) int main(int argc, char *argv[]) { int n, myid, numprocs, i, j, nsteps=10; float a[arysize2+1][arysize2+1],b[arysize2+1][arysize2+1]; double starttime,endtime; /* float send_buf[arysize2],recv_buf[arysize2] ;*/ int col_tag,row_tag,send_col,send_row,recv_col,recv_row; int col_neighbor,row_neighbor; MPI_Comm comm2d; MPI_Datatype newtype; int zero=0,one=1; int right,left,down,top,top_bound,left_bound; 2017年9月 MPI并行程序设计
int dims[2],begin_row,end_row; MPI_Status status; int periods[2]; int dims[2],begin_row,end_row; MPI_Status status; MPI_Init(&argc,&argv); dims[0] = 2; dims[1] = 2; periods[0]=0; periods[1]=0; MPI_Cart_create( MPI_COMM_WORLD, 2, dims, periods, 0,&comm2d); MPI_Comm_rank(comm2d,&myid); MPI_Type_vector( arysize2, 1, arysize2+1,MPI_FLOAT,&newtype); MPI_Type_commit( &newtype ); MPI_Cart_shift( comm2d, 0, 1, &left, &right); MPI_Cart_shift( comm2d, 1, 1, &down, &top); 2017年9月 MPI并行程序设计
for(i=0;i<arysize2+1;i++) for(j=0;j<arysize2+1;j++) a[i][j]=0.0; if (top == MPI_PROC_NULL) { for ( i=0;i<arysize2+1;i++) a[0][i]=8.0; } if (down == MPI_PROC_NULL) for ( i=0;i<arysize2+1;i++) a[arysize2][i]=8.0; 2017年9月 MPI并行程序设计
if (left == MPI_PROC_NULL) { for ( i=0;i<arysize2+1;i++) a[i][0]=8.0; } if (right == MPI_PROC_NULL) for ( i=0;i<arysize2+1;i++) a[i][arysize2]=8.0; col_tag = 5; row_tag = 6; printf("Laplace Jacobi#C(BLOCK,BLOCK)#myid=%d#step=%d#total arysize=%d*%d\n",myid,nsteps,arysize,arysize); 2017年9月 MPI并行程序设计
if (top == MPI_PROC_NULL) top_bound=0; left_bound=1; if (top == MPI_PROC_NULL) top_bound=0; if (left == MPI_PROC_NULL) left_bound=0; starttime=MPI_Wtime(); for (n=0; n<nsteps; n++) { MPI_Sendrecv( &a[1][left_bound], arysize2, MPI_FLOAT, top, row_tag,& a[arysize2][left_bound], arysize2, MPI_FLOAT, down, row_tag, comm2d, &status ); MPI_Sendrecv( &a[arysize2-1][left_bound], arysize2, MPI_FLOAT, down, row_tag,& a[0][left_bound], arysize2, MPI_FLOAT, top, row_tag, comm2d, &status ); 2017年9月 MPI并行程序设计
for ( i=1;i<arysize2;i++) for (j=1;j<arysize2;j++) MPI_Sendrecv( &a[top_bound][1], 1,newtype, left, col_tag,& a[top_bound][arysize2], 1, newtype, right, col_tag, comm2d, &status ); MPI_Sendrecv( &a[top_bound][arysize2-1], 1, newtype, right, col_tag, &a[top_bound][0], 1, newtype, left, col_tag, comm2d, &status ); for ( i=1;i<arysize2;i++) for (j=1;j<arysize2;j++) b[i][j] = (a[i][j+1]+a[i][j-1])*0.25; b[i][j] = (a[i+1][j]+a[i-1][j])*0.25+b[i][j]; a[i][j] = b[i][j]; } 2017年9月 MPI并行程序设计
endtime=MPI_Wtime(); printf("elapse time=%f\n",endtime-starttime); MPI_Type_free( &newtype ); MPI_Comm_free( &comm2d ); MPI_Finalize(); } 2017年9月 MPI并行程序设计
并行拉格朗日元算法
串行拉格朗日元法原理(1) 拉格朗日元法的主要特点是: 它是一种显式解法。显式解法容易处理非线性和大变形问题,而且当结构出现破坏时,数值上也比较容易处理。 它不形成整体刚度矩阵。一方面节省了存储空间,相同条件下,能计算更大规模的问题。另一方面,由于不必进行矩阵求逆的运算,在算法上更容易处理。 它采用动态松弛法来求解静力问题。 2017年9月 MPI并行程序设计
串行拉格朗日元法原理(2) 基本方程 几何方程:对于给定的速度场vi,则应变率张量ξij为 转动率张量ωij为 2017年9月 MPI并行程序设计
串行拉格朗日元法原理(3) 本构方程 式中[ ij]表示Jaumann应力速率张量,Hij表示一个应力应变关系函数,k为考虑加载过程的参数。 2017年9月 MPI并行程序设计
串行拉格朗日元法原理(4) 运动方程:对于给定的应力场σij,材料密度,单位质量所受体积力bi,则满足 2017年9月 MPI并行程序设计
串行拉格朗日元法原理(5) 空间离散 三角形 四边形 四面体 六面体 2017年9月 MPI并行程序设计
串行拉格朗日元法原理(6) 空间导数的近似 采用不规则网格上的差分法 对于一个给定的初始速度场vi,假定四面体单元内vi为线性分布,则vi在j方向的导数vi,j是一个常量,外表面的单位法向向量nj在每个面上为常量,应用高斯公式可得到 2017年9月 MPI并行程序设计
串行拉格朗日元法原理(7) 时间导数的近似 中心差分法 2017年9月 MPI并行程序设计
主程序流程图 2017年9月 MPI并行程序设计
并行拉格朗日元法设计(1) 对算法的并行性分析 机群硬件的特点 是否可以并行 哪些部分可以并行 进程的数据是私有的 通信延时大 数据如何存储、访问和交换 2017年9月 MPI并行程序设计
并行拉格朗日元法设计(2) 并行性分析 在每一个时步的计算中,计算应变增量和应力是逐单元进行的,节点力和节点速度也是逐个进行计算。 因此,拉格朗日元法并行化的基本策略是区域分解法,即把整个计算区域划分为几个子区域,分配给不同的进程进行计算。 2017年9月 MPI并行程序设计
并行拉格朗日元法设计(3) 网格划分和数据通信密切相关。 单元划分和节点划分 2017年9月 MPI并行程序设计
并行拉格朗日元法设计(4) 网格划分工具METIS 由G. Karypis和V. Kumar开发 多级k划分(Multilevel k-way Partition)算法 2017年9月 MPI并行程序设计
并行拉格朗日元法设计(5) 共享优先算法 计算与通信的重叠 通信比较耗时,尤其网络性能比较差的时候 希望在等待通信完成的同时,能进行一些计算工作 先共享节点,后内部节点 先共享单元,后内部单元 2017年9月 MPI并行程序设计
并行拉格朗日元法设计(6) 通信模式 主从(Master and Slave)模式和对等(Peer to Peer)模式 2017年9月 MPI并行程序设计
2017年9月 MPI并行程序设计
网格1在8个计算结点上的划分 2017年9月 MPI并行程序设计
2017年9月 MPI并行程序设计
并行效率 2017年9月 MPI并行程序设计
并行程序设计的一些建议 优化并行算法 大并行粒度 顾及负载平衡 尽量减少通信次数 避免大消息(1M) 避免大消息打包 避免消息缓冲区的溢出,且效率较低 避免大消息打包 内存拷贝开销大 2017年9月 MPI并行程序设计
谢谢! 2017年9月 MPI并行程序设计