670 likes | 897 Views
MPI 分布内存并行 程序开发. 程序参数说明. MPI_Init (). MPI_Comm_rank () MPI_Comm_size (). 建立新的通信器、定义新的数据类型和进程拓扑结构. 应用程序实体: 计算控制程序体; 进程间通信;. 退出 MPI 系统. MPI_Finalize (). End. 一般的 MPI 程序设计流程图. 4 个 MPI 基本指令. #include <stdio.h> #include <stdlib.h> #include <mpi.h> int nproc, myid; main ( argc, argv)
E N D
程序参数说明 MPI_Init() MPI_Comm_rank () MPI_Comm_size () 建立新的通信器、定义新的数据类型和进程拓扑结构 • 应用程序实体: • 计算控制程序体; • 进程间通信; 退出MPI系统 MPI_Finalize () End 一般的MPI程序设计流程图
4个MPI基本指令 #include <stdio.h> #include <stdlib.h> #include <mpi.h> int nproc, myid; main ( argc, argv) int argc; char **argv; { MPI_Init(&argc, &argv); MPI_Comm_size (MPI_COMM_WORLD, &nproc); MPI_Comm_rank (MPI_COMM_WORLD, &myid); . . . . . . MPI_Finalize(); return 0; } 启动程序在多个core上的并行计算工作 获取参与并行计算的CPU数目(nproc) 获取自身ID (myid) from 0 结束并行计算工作
点对点通信函数 传送机制(两种): 阻塞方式,它必须等到消息从本地送出之后才可以执行后续的语句,保证了缓冲区等资源的可再用性; 非阻塞方式,它不须等到消息从本地送出就可以执行后续的语句,但非阻塞调用的返回并不保证资源的可再用性。
阻塞发送 阻塞接收 • 阻塞通信与非阻塞通信 开始 开始 消息成功发出 消息成功接收 结束 结束 缓冲区数据可使用 缓冲区可释放 • 阻塞消息发送与接收
非阻塞发送 非阻塞接收 启动发送 启动接收 发 送 消 息 接 收 消 息 立即返回 立即返回 计算 与 通信 重叠 计 算 计 算 通信完成 通信完成 释放发送缓冲区 引用接收数据 非阻塞消息发送与接收
阻塞通信正确返回后,其后果是: - 该调用要求的通信操作已正确完成 - 该调用的缓冲区可用 • 消息信封要匹配 • 接收到的消息是最早发送的 • 非阻塞通信主要用于计算和通信的重叠,从而提高整个程序执行的效率。
MPI消息传递函数参数 MPI点对点通信函数的参数格式一般如下所示:
请求(request) • 这个参数用于非阻塞发送和非阻塞接收操作。由于非阻塞操作返回后,数据可能继续存在缓冲中,由此需要一种机制来检测资源是否可用。根据该变量调用其它函数完成消息的实际发送和接收。在C程序中,这个参数是指向MPI_Request结构的指针。
通讯模式(4种): • 标准通信模式(MPI_SEND) • 缓存通信模式(MPI_BSEND) • 同步通信模式(MPI_SSEND) • 就绪通信模式(MPI_RSEND)
标准(standard)模式:对数据的缓冲由具体MPI实现决定,与用户程序无关;标准(standard)模式:对数据的缓冲由具体MPI实现决定,与用户程序无关; 发送操作的正确返回而不要求接收操作收到发送的数据。 S R 1
缓冲区(buffered)模式:用户定义,使用和回收缓冲区,不管接收操作是否启动,发送操作都可以执行,但是必须保证缓冲区可用。缓冲区(buffered)模式:用户定义,使用和回收缓冲区,不管接收操作是否启动,发送操作都可以执行,但是必须保证缓冲区可用。 S R 1 2 缓冲区
同步(synchronous)模式:开始不依赖于接收进程相应的操作是否启动,但必须等到接受开始启动发送才可以返回同步(synchronous)模式:开始不依赖于接收进程相应的操作是否启动,但必须等到接受开始启动发送才可以返回 1 2 3 S R
就绪(ready)模式:只有当接收操作已经启动时,才可以在发送进程启动发送操作,否则发送将出错。就绪(ready)模式:只有当接收操作已经启动时,才可以在发送进程启动发送操作,否则发送将出错。 S R 1 2
例、死锁的发送接收序列 CALL MPI_COMM_RANK(comm,rank,ierr) IF (rank.EQ.0) THEN CALL MPI_RECV(recvbuf,count,MPI_REAL,1, tag,comm,status,ierr) CALL MPI_SEND(sendbuf,count,MPI_REAL,1, tag,comm,ierr) ELSE IF (rank.EQ.1) CALL MPI_RECV(recvbuf,count,MPI_REAL,0, tag,comm,status,ierr) CALL MPI_SEND(sendbuf,count,MPI_REAL,0, tag,comm,ierr) ENDIF
进程 0 进程1 从进程1接收消息A 从进程0接收消息B 向进程1发送消息C 向进程0发送消息D A D C B
例、不安全的发送接收序列 CALL MPI_COMM_RANK(comm,rank,ierr) IF (rank.EQ.0) THEN CALL MPI_SEND(sendbuf,count,MPI_REAL,1, tag,comm,ierr) CALL MPI_RECV(recvbuf,count,MPI_REAL,1, tag,comm,status,ierr) ELSE IF (rank.EQ.1) CALL MPI_SEND(sendbuf,count,MPI_REAL,0, tag,comm,ierr) CALL MPI_RECV(recvbuf,count,MPI_REAL,0, tag,comm,status,ierr) ENDIF
进程 0 进程1 从进程1发送消息A 从进程0发送消息B 向进程1接收消息C 向进程0接收消息D A D C 系统缓冲区 B
程序、安全的发送接收序列 CALL MPI_COMM_RANK(comm,rank,ierr) IF (rank.EQ.0) THEN CALL MPI_SEND(sendbuf,count,MPI_REAL,1, tag,comm,ierr) CALL MPI_RECV(recvbuf,count,MPI_REAL,1, tag,comm,status,ierr) ELSE IF (rank.EQ.1) CALL MPI_RECV(recvbuf,count,MPI_REAL,0, tag,comm,status,ierr) CALL MPI_SEND(sendbuf,count,MPI_REAL,0, tag,comm,ierr) ENDIF
进程 0 进程1 从进程1发送消息A 从进程0接收消息B 向进程1接收消息C 向进程0发送消息D A D C B
例 循环-死锁 • clock=(myrank+1)%groupsize; • anticlock=(myrank+groupsize-1)%groupsize; • MPI_Send(buf1,LENGTH,MPI_CHAR,clock,tag,MPI_COMM_WORLD); • MPI_Recv(buf2,LENGTH,MPI_CHAR,anticlock,tag,MPI_COMM_WORLD,&status); 0 1 2
改进: • MPI_Isend(buf1,LENGTH,MPI_CHAR,clock,tag,MPI_COMM_WORLD,&request); • MPI_Recv(buf2,LENGTH,MPI_CHAR,anticlock,tag,MPI_COMM_WORLD,&status); • MPI_Wait(&request,&status); • --------------------------------- • MPI_Irecv(buf2,LENGTH,MPI_CHAR,anticlock,tag,MPI_COMM_WORLD,&request); • MPI_Send(buf2,LENGTH,MPI_CHAR,clock,tag,MPI_COMM_WORLD); • MPI_Wait(&request,&status);
集合通信函数 • 集合通信是包含在通信因子中的所有进程都 参加操作。 • 集合通信一般实现三个功能 通信:组内数据的传输 同步:组内所有进程在特定的地点在执行 进度上取得一致 计算:对给定的数据完成一定的操作
集合操作的三种类型: • 同步(barrier):集合中所有进程都到达后,每个进程再接着运行; • 数据传递:广播(broadcast)、分散(scatter)、收集(gather)、全部到全部(alltoall); • 规约(reduction):集合中的其中一个进程收集所有进程的数据并计算(如:求最大值、求最小值、加、乘等);
集合通信函数 • MPI_Barrier • MPI_Bcast • MPI_Scatter • MPI_Gather • MPI_Scan • MPI_Reduce
MPI_Barrier() • 在组中建立一个同步栅栏。当每个进程都到达MPI_Barrier调用后,程序才接着往下执行: • MPI_Barrier (comm)
程序、同步示例 #include “mpi.h” #include “test.h” #include <stdlib.h> #include <stdio.h> int main(int argc,char * * argv) { int rank,size,I; int *table; int errors=0; MPI_Aint address; MPI_Datatype type,newtype; int lens; MPI_Init( &argc,&argv); MPI_Comm_rank (MPI_COMM_WORLD,&rank); MPI_Comm_size (MPI_COMM_WORLD,&size);
/*Make data table */ table =(int *)calloc(size,sizeof(int)); table[rank]=rank+1; /*准备要广播的数据*/ MPI_Barrier (MPI_COMM_WORLD); /*将数据广播出去*/ for (i=0;i<size,i++) MPI_Bcast( &table[i],1,MPI_INT,i,MPI_COMM_WORLD); /*检查接收到的数据的正确性*/ for (i=0;i<size,i++) if (table[i]!=i+1) errors++; MPI_Barrier(MPI_COMM_WORLD); /*检查完毕后执行一次同步*/ …… /*其他的计算*/ MPI_Finalize(); }
MPI_Bcast() • 从指定的一个根进程中把数据广播发送给组中的所有其它进程: • MPI_Bcast (*buffer,count,datatype,root,comm) • 对于root进程:buffer既是接收缓冲又是发送缓冲;对于其他进程:buffer就是接收缓冲。
程序、广播程序示例 #include <stdio.h> #include “mpi.h” int main (argc,argv) int argc; Char * * argv; { int rank,value; MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&rank);
do{ if (rank==0) /*进程0读入需要广播的数据*/ scanf(“%d”,&value); /*将该数据广播出去*/ MPI_Bcast(&value,1,MPI_INT,0,MPI_COMM_WORLD); /*各进程打印收到的数据*/ printf(“Process %d got %d \n”,rank,value); }while(value>=0); MPI_Finalize(); return 0; }
MPI_Scatter() • 把根进程中的数据分散发送给组中的所有进程(包括自己): • MPI_Scatter (*sendbuf,sendcnt,sendtype, *recvbuf, recvcnt,recvtype,root,comm) root用MPI_Send(sendbuf, sendcount·n, sendtype, …)发送一个消息。这个消息分成n个相等的段,第i个段发送到进程组的第i个进程,sendcnt必须要和recvcnt相同。 与广播不同,Root进程向各个进程传递的消息可以不同。
MPI_Gather() • 在组中指定一个进程收集组中所有进程发送来的消息,这个函数操作与MPI_Scatter函数操作相反: • MPI_Gather (*sendbuf,sendcnt,sendtype, *recvbuf, ecvcount,recvtype,root,comm)
MPI_Reduce() • 在组内所有的进程中,执行一个规约操作,并把结果存放在指定的一个进程中: • MPI_Reduce (*sendbuf,*recvbuf,count,datatype, op,root, comm) • MPI缺省定义了如下的规约操作,用户可根据自己的需要用MPI_Op_create函数创建新的规约操作:
程序、规约示例 #include “mpi.h” #include <stdio.h> #include <math.h> double f(double x);/*定义函数f(x) */ { return(4.0/(1.0+x*x)); } 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_MAXPROCESSOR_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); fprint(stdout,”Process %d of %d on % s\n”,myid,numprocs, processor_name); n=0; if (myid==0) { printf(“Please give N=”); scanf(&n); startwtime=MPI_Wtime(); } /*将n值广播出去*/ MPI_Bcast(&n,1,MPI_INT,0,MPI_COMM_WORLD);
h=1.0/(double) n; sum=0.0; for(i=myid+1;i<=n;i+=numprocs) /* 每一个进程计算一部分矩形的面积,若进程总数numprocs为4, 将 0-1区间划分为100个矩形,则各个进程分别计算矩形块 0进程 1,5,9,13,……,97 1进程 2,6,10,14,……,98 2进程 3,7,11,15,……,99 3进程 4,8,12,16,……,100 */ { x=h*((double)i-0.5); sum+=f(x); } mypi=h*sum; /*各进程并行计算得到的部分和*/
/*将部分和累加得到所有矩形的面积,该面积和即为近似PI值*/ 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); fflush(stdout); } MPI_Finalize(); }
MPI_Scan() • 用来对分布在进程组上的数据执行前缀归约: • MPI_Scan (*sendbuf,*recvbuf,count, datatype,op,comm)
群集函数的特点: • 通讯因子中所有进程都要调用 • 除了MPI_Barrier(),其他函数使用类似标准阻塞的通信模式。一个进程一旦结束了它所参与的群集操作就从群集例程中返回,并不保证其他进程执行该群集例程已经完成。 • 一个群集例程是不是同步操作取决于实现。
MPI并行程序的两种基本模式 • 对等模式的MPI程序设计 • 主从模式的MPI程序设计
一.对等模式的MPI程序设计 1.问题描述——Jacobi迭代 Jacobi迭代是一种比较常见的迭代方法,简单的说,Jacobi迭代得到的新值是原来旧值点相邻数值点的平均。 Jacobi迭代的局部性很好,可以取得很高的并行性。将参加迭代的数据按块分割后,各块之间除了相邻的元素需要通信外,在各块的内部可以完全独立的并行计算。
程序串行表示的Jacobi迭代 …… REAL A(N+1,N+1),B(N+1,N+1) …… DO K=1,STEP DO J=1,N DO I=1,N B(I,J)=0.25*(A(I-1,J)+A(I+1,J)+A(I,J+1)+A(I,J-1)) END DO END DO DO J=1,N DO I=1,N A(I,J)=B(I,J) END DO END DO END DO
2.用MPI程序实现Jacobi迭代 为了并行求解,这里将参加迭代的数据按列进行分割,假设有4个进程同时并行计算,数据的分割结果如图: