63
MPI MPI 分分分分分分 分分分分分分 分分分分 分分分分

MPI 分布内存并行 程序开发

  • Upload
    naomi

  • View
    141

  • Download
    5

Embed Size (px)

DESCRIPTION

MPI 分布内存并行 程序开发. 第四章 点对点通信函数. 传送机制(两种): 阻塞方式 ,它必须等到消息从本地 送出 之后才可以执行后续的语句,保证了缓冲区等资源的可再用性; 非阻塞方式 ,它不须等到消息从本地送出就可以执行后续的语句,但非阻塞调用的返回并不保证资源的可再用性。. 阻塞通信正确返回后,其后果是: - 该调用要求的通信操作已正确完成 - 该调用的缓冲区可用 消息信封要匹配 接收到的消息是最早发送的 非阻塞通信主要用于计算和通信的重叠,从而提高整个程序执行的效率。. MPI 消息传递函数参数. - PowerPoint PPT Presentation

Citation preview

Page 1: MPI 分布内存并行 程序开发

MPIMPI 分布内存并行分布内存并行程序开发程序开发

Page 2: MPI 分布内存并行 程序开发

第四章 点对点通信函数第四章 点对点通信函数传送机制(两种):阻塞方式,它必须等到消息从本地送出之后才可以执行后续的语句,保证了缓冲区等资源的可再用性;非阻塞方式,它不须等到消息从本地送出就可以执行后续的语句,但非阻塞调用的返回并不保证资源的可再用性。

Page 3: MPI 分布内存并行 程序开发

阻塞通信正确返回后,其后果是:阻塞通信正确返回后,其后果是: - - 该调用要求的通信操作已正确完成该调用要求的通信操作已正确完成 - - 该调用的缓冲区可用该调用的缓冲区可用 消息信封要匹配消息信封要匹配 接收到的消息是最早发送的接收到的消息是最早发送的 非阻塞通信主要用于计算和通信的重叠,非阻塞通信主要用于计算和通信的重叠,从而提高整个程序执行的效率。从而提高整个程序执行的效率。

Page 4: MPI 分布内存并行 程序开发

MPI 点对点通信函数的参数格式一般如下所示:阻塞发送: MPI_Send(buffer,count,type,dest,tag,comm)

非阻塞发送: MPI_Isend(buffer,count,type,dest,tag,comm,request)

阻塞接收: MPI_Recv(buffer,count,type,source,tag,comm,status)

非阻塞接收: MPI_Irecv(buffer,count,type,source,tag,comm,request)

MPIMPI 消息传递函数参数消息传递函数参数

Page 5: MPI 分布内存并行 程序开发

请求(请求( requestrequest)) 这个参数用于非阻塞发送和非阻塞接收操作。这个参数用于非阻塞发送和非阻塞接收操作。由于非阻塞操作返回后,数据可能继续存在缓由于非阻塞操作返回后,数据可能继续存在缓冲中,由此需要一种机制来检测资源是否可用。冲中,由此需要一种机制来检测资源是否可用。根据该变量调用其它函数完成消息的实际发送根据该变量调用其它函数完成消息的实际发送和接收。在和接收。在 CC 程序中,这个参数是指向程序中,这个参数是指向

MPI_RequestMPI_Request 结构的指针。结构的指针。

Page 6: MPI 分布内存并行 程序开发

通讯模式( 4 种): 标准通信模式标准通信模式 ((MPI_SEND)MPI_SEND) 缓存通信模式缓存通信模式 ((MPI_BSEND)MPI_BSEND) 同步通信模式同步通信模式 ((MPI_SSEND)MPI_SSEND) 就绪通信模式就绪通信模式 ((MPI_RSEND)MPI_RSEND)

Page 7: MPI 分布内存并行 程序开发

标准(标准( standardstandard ))模式:对数据的缓冲模式:对数据的缓冲由具体由具体 MPIMPI 实现决定,与用户程序无关;实现决定,与用户程序无关; 发送操作的正确返回而不要求接收操作发送操作的正确返回而不要求接收操作收到发送的数据。收到发送的数据。

SS RR1

Page 8: MPI 分布内存并行 程序开发

缓冲区( buffered )模式:用户定义,使用和回收缓冲区,不管接收操作是否启动,发送操作都可以执行,但是必须保证缓冲区可用。

SS RR

1

缓冲区 2

Page 9: MPI 分布内存并行 程序开发

同步 (synchronous) 模式:开始不依赖于接收进程相应的操作是否启动,但必须等到接受开始启动发送才可以返回

SS RR1

2

3

Page 10: MPI 分布内存并行 程序开发

就绪 (ready) 模式:只有当接收操作已经启动时,才可以在发送进程启动发送操作,否则发送将出错。

SS RR1

2

Page 11: MPI 分布内存并行 程序开发

例例 33 、死锁的发送接收序列、死锁的发送接收序列 CALL MPI_COMM_RANK(comm,rank,ierr)CALL MPI_COMM_RANK(comm,rank,ierr) IF (rank.EQ.0) THENIF (rank.EQ.0) THEN CALL MPI_RECV(recvbuf,count,MPI_REAL,1,CALL MPI_RECV(recvbuf,count,MPI_REAL,1, tag,comm,status,ierr)tag,comm,status,ierr) CALL MPI_SEND(sendbuf,count,MPI_REAL,1,CALL MPI_SEND(sendbuf,count,MPI_REAL,1, tag,comm,ierr)tag,comm,ierr) ELSE IF (rank.EQ.1)ELSE IF (rank.EQ.1) CALL MPI_RECV(recvbuf,count,MPI_REAL,0,CALL MPI_RECV(recvbuf,count,MPI_REAL,0, tag,comm,status,ierr)tag,comm,status,ierr) CALL MPI_SEND(sendbuf,count,MPI_REAL,0,CALL MPI_SEND(sendbuf,count,MPI_REAL,0, tag,comm,ierr)tag,comm,ierr) ENDIFENDIF

Page 12: MPI 分布内存并行 程序开发

进程 0 进程 1

从进程 1 接收消息 A

向进程 1 发送消息 C

从进程 0 接收消息 B

向进程 0 发送消息 D

A

B

C D

Page 13: MPI 分布内存并行 程序开发

例例 44 、不安全的发送接收序列、不安全的发送接收序列CALL MPI_COMM_RANK(comm,rank,ierr)CALL MPI_COMM_RANK(comm,rank,ierr) IF (rank.EQ.0) THENIF (rank.EQ.0) THEN CALL MPI_SEND(sendbuf,count,MPI_REAL,1,CALL MPI_SEND(sendbuf,count,MPI_REAL,1, tag,comm,ierr)tag,comm,ierr) CALL MPI_RECV(recvbuf,count,MPI_REAL,1,CALL MPI_RECV(recvbuf,count,MPI_REAL,1, tag,comm,status,ierr) tag,comm,status,ierr) ELSE IF (rank.EQ.1)ELSE IF (rank.EQ.1) CALL MPI_SEND(sendbuf,count,MPI_REAL,0,CALL MPI_SEND(sendbuf,count,MPI_REAL,0, tag,comm,ierr)tag,comm,ierr) CALL MPI_RECV(recvbuf,count,MPI_REAL,0,CALL MPI_RECV(recvbuf,count,MPI_REAL,0, tag,comm,status,ierr) tag,comm,status,ierr) ENDIFENDIF

Page 14: MPI 分布内存并行 程序开发

进程 0 进程 1

从进程 1 发送消息 A

向进程 1 接收消息 C

从进程 0 发送消息 B

向进程 0 接收消息 D

A

B

C D系统缓冲区

Page 15: MPI 分布内存并行 程序开发

程序程序 55 、安全的发送接收序列、安全的发送接收序列CALL MPI_COMM_RANK(comm,rank,ierr)CALL MPI_COMM_RANK(comm,rank,ierr) IF (rank.EQ.0) THENIF (rank.EQ.0) THEN CALL MPI_SEND(sendbuf,count,MPI_REAL,1,CALL MPI_SEND(sendbuf,count,MPI_REAL,1, tag,comm,ierr)tag,comm,ierr) CALL MPI_RECV(recvbuf,count,MPI_REAL,1,CALL MPI_RECV(recvbuf,count,MPI_REAL,1, tag,comm,status,ierr) tag,comm,status,ierr) ELSE IF (rank.EQ.1)ELSE IF (rank.EQ.1) CALL MPI_RECV(recvbuf,count,MPI_REAL,0,CALL MPI_RECV(recvbuf,count,MPI_REAL,0, tag,comm,status,ierr)tag,comm,status,ierr) CALL MPI_SEND(sendbuf,count,MPI_REAL,0,CALL MPI_SEND(sendbuf,count,MPI_REAL,0, tag,comm,ierr) tag,comm,ierr) ENDIFENDIF

Page 16: MPI 分布内存并行 程序开发

进程 0 进程 1

从进程 1 发送消息 A

向进程 1 接收消息 C

从进程 0 接收消息 B

向进程 0 发送消息 D

A

B

C D

Page 17: MPI 分布内存并行 程序开发

例子例子 66 clock=(myrank+1)%groupsize;clock=(myrank+1)%groupsize; anticlock=(myrank+groupsize-1)%groupsize;anticlock=(myrank+groupsize-1)%groupsize; MPI_Send(buf1,LENGTH,MPI_CHAR,clock,tag,MPI_COMMPI_Send(buf1,LENGTH,MPI_CHAR,clock,tag,MPI_COM

M_WORLD);M_WORLD); MPI_Recv(buf2,LENGTH,MPI_CHAR,anticlock,tag,MPI_CMPI_Recv(buf2,LENGTH,MPI_CHAR,anticlock,tag,MPI_C

OMM_WORLD,&status);OMM_WORLD,&status);

00 11 22

Page 18: MPI 分布内存并行 程序开发

改进:改进: MPI_Isend(buf1,LENGTH,MPI_CHAR,clock,tag,MPI_COMM_WORLD,&reqMPI_Isend(buf1,LENGTH,MPI_CHAR,clock,tag,MPI_COMM_WORLD,&req

uest);uest); MPI_Recv(buf2,LENGTH,MPI_CHAR,anticlock,tag,MPI_COMM_WORLD,&MPI_Recv(buf2,LENGTH,MPI_CHAR,anticlock,tag,MPI_COMM_WORLD,&

status);status); MPI_Wait(&request,&status);MPI_Wait(&request,&status);

------------------------------------------------------------------

MPI_Irecv(buf2,LENGTH,MPI_CHAR,anticlock,tag,MPI_COMM_WORLD,&rMPI_Irecv(buf2,LENGTH,MPI_CHAR,anticlock,tag,MPI_COMM_WORLD,&request);equest);

MPI_Send(buf2,LENGTH,MPI_CHAR,clock,tag,MPI_COMM_WORLD);MPI_Send(buf2,LENGTH,MPI_CHAR,clock,tag,MPI_COMM_WORLD); MPI_Wait(&request,&status);MPI_Wait(&request,&status);

Page 19: MPI 分布内存并行 程序开发

第五章 集合通信函数第五章 集合通信函数 集合通信是包含在通信因子中的所有进程都 集合通信是包含在通信因子中的所有进程都 参加操作。参加操作。 集合通信一般实现三个功能集合通信一般实现三个功能 通信:组内数据的传输通信:组内数据的传输 同步:组内所有进程在特定的地点在执行同步:组内所有进程在特定的地点在执行 进度上取得一致进度上取得一致 计算:对给定的数据完成一定的操作计算:对给定的数据完成一定的操作

Page 20: MPI 分布内存并行 程序开发

集合操作的三种类型:集合操作的三种类型:同步同步 ((barrier)barrier) ::集合中所有进程都到达集合中所有进程都到达后,每个进程再接着运行;后,每个进程再接着运行;数 据 传 递 : 广 播数 据 传 递 : 广 播 ((broadcast)broadcast) 、、 分 散分 散

((scatter)scatter) 、、收集收集 ((gather)gather) 、、全部到全部全部到全部((alltoall)alltoall);;

规约规约 ((reduction)reduction) ::集合中的其中一个进集合中的其中一个进程收集所有进程的数据并计算(如:求程收集所有进程的数据并计算(如:求最大值、求最小值、加、乘等);最大值、求最小值、加、乘等);

Page 21: MPI 分布内存并行 程序开发

集合通信函数集合通信函数 MPI_BarrierMPI_Barrier MPI_BcastMPI_Bcast MPI_ScatterMPI_Scatter MPI_GatherMPI_Gather MPI_ScanMPI_Scan MPI_ReduceMPI_Reduce

Page 22: MPI 分布内存并行 程序开发

MPI_Barrier()MPI_Barrier()

在组中建立一个同步栅栏。当每个进程都到达在组中建立一个同步栅栏。当每个进程都到达MPI_BarrierMPI_Barrier 调用后,程序才接着往下执行:调用后,程序才接着往下执行:

MPI_Barrier (comm) MPI_Barrier (comm)

Page 23: MPI 分布内存并行 程序开发

程序程序 77 、同步示例、同步示例##include “mpi.h”include “mpi.h”##include “test.h”include “test.h”##include <stdlib.h>include <stdlib.h>##include <stdio.h>include <stdio.h>int main(int argc,char * * argv)int main(int argc,char * * argv){ { int rank,size,I;int rank,size,I; int *table;int *table; int errors=0;int errors=0; MPI_Aint address;MPI_Aint address; MPI_Datatype type,newtype;MPI_Datatype type,newtype; int lens;int lens; MPI_Init( &argc,&argv);MPI_Init( &argc,&argv); MPI_Comm_rank (MPI_COMM_WORLD,&rank);MPI_Comm_rank (MPI_COMM_WORLD,&rank); MPI_Comm_size (MPI_COMM_WORLD,&size);MPI_Comm_size (MPI_COMM_WORLD,&size);

Page 24: MPI 分布内存并行 程序开发

/*/*Make data table */Make data table */ table =(int *)calloc(size,sizeof(int));table =(int *)calloc(size,sizeof(int)); table[rank]=rank+1; /*table[rank]=rank+1; /* 准备要广播的数据准备要广播的数据 */*/ MPI_Barrier (MPI_COMM_WORLD);MPI_Barrier (MPI_COMM_WORLD); /*/* 将数据广播出去将数据广播出去 */*/ for (i=0;i<size,i++)for (i=0;i<size,i++) MPI_Bcast( &table[i],1,MPI_INT,i,MPI_COMM_WORLD);MPI_Bcast( &table[i],1,MPI_INT,i,MPI_COMM_WORLD); /*/* 检查接收到的数据的正确性检查接收到的数据的正确性 */*/ for (i=0;i<size,i++)for (i=0;i<size,i++) if (table[i]!=i+1) errors++;if (table[i]!=i+1) errors++; MPI_Barrier(MPI_COMM_WORLD); /*MPI_Barrier(MPI_COMM_WORLD); /* 检查完毕后执行一次同步检查完毕后执行一次同步 */*/ …… …… /*/* 其他的计算其他的计算 */*/ MPI_Finalize();MPI_Finalize();}}

Page 25: MPI 分布内存并行 程序开发

MPI_Bcast()MPI_Bcast() 从指定的一个根进程中把数据广播发送给组中从指定的一个根进程中把数据广播发送给组中的所有其它进程:的所有其它进程: MPI_Bcast (*buffer,count,datatype,root,comm) MPI_Bcast (*buffer,count,datatype,root,comm) 对于对于 rootroot 进程:进程: bufferbuffer 既是接收缓冲又是发送既是接收缓冲又是发送缓冲;对于其他进程:缓冲;对于其他进程: bufferbuffer 就是接收缓冲。就是接收缓冲。

Page 26: MPI 分布内存并行 程序开发
Page 27: MPI 分布内存并行 程序开发

程序程序 88 、广播程序示例、广播程序示例##include <stdio.h>include <stdio.h>#include “mpi.h”#include “mpi.h”

int main (argc,argv)int main (argc,argv)int argc;int argc;Char * * argv;Char * * argv;{{ int rank,value;int rank,value; MPI_Init(&argc,&argv);MPI_Init(&argc,&argv); MPI_Comm_rank(MPI_COMM_WORLD,&rank);MPI_Comm_rank(MPI_COMM_WORLD,&rank);

Page 28: MPI 分布内存并行 程序开发

do{do{ if (rank==0) /*if (rank==0) /* 进程进程 00 读入需要广播的数据读入需要广播的数据 */*/ scanf(“%d”,&value);scanf(“%d”,&value); /*/* 将该数据广播出去将该数据广播出去 */*/ MPI_Bcast(&value,1,MPI_INT,0,MPI_COMM_WORLD);MPI_Bcast(&value,1,MPI_INT,0,MPI_COMM_WORLD); /*/* 各进程打印收到的数据各进程打印收到的数据 */*/ printf(“Process %d got %d \n”,rank,value);printf(“Process %d got %d \n”,rank,value); }while(value>=0);}while(value>=0); MPI_Finalize();MPI_Finalize(); return 0;return 0;}}

Page 29: MPI 分布内存并行 程序开发

MPI_Scatter()MPI_Scatter() 把根进程中的数据分散发送给组中的所有进程把根进程中的数据分散发送给组中的所有进程(包括自己):(包括自己): MPI_Scatter (*sendbuf,sendcnt,sendtype,MPI_Scatter (*sendbuf,sendcnt,sendtype, *recvbuf, recvcnt,recvtype,root,comm) *recvbuf, recvcnt,recvtype,root,comm) rootroot 用用 MPI_Send(sendbuf, sendcount·n, MPI_Send(sendbuf, sendcount·n,

sendtype, …)sendtype, …) 发送一个消息。这个消息分成发送一个消息。这个消息分成 nn个相等的段,第个相等的段,第 ii 个段发送到进程组的第个段发送到进程组的第 ii 个进个进程,程, sendcntsendcnt 必须要和必须要和 recvcntrecvcnt 相同。相同。

Page 30: MPI 分布内存并行 程序开发
Page 31: MPI 分布内存并行 程序开发

MPI_Gather()MPI_Gather() 在组中指定一个进程收集组中所有进程发送来的在组中指定一个进程收集组中所有进程发送来的消息,这个函数操作与消息,这个函数操作与 MPI_ScatterMPI_Scatter 函数操作相函数操作相反:反: MPI_Gather (*sendbuf,sendcnt,sendtype,MPI_Gather (*sendbuf,sendcnt,sendtype, *recvbuf, ecvcount,recvtype,root,comm) *recvbuf, ecvcount,recvtype,root,comm)

Page 32: MPI 分布内存并行 程序开发
Page 33: MPI 分布内存并行 程序开发

MPI_Reduce()MPI_Reduce() 在组内所有的进程中,执行一个规约操作,并在组内所有的进程中,执行一个规约操作,并把结果存放在指定的一个进程中:把结果存放在指定的一个进程中: MPI_Reduce MPI_Reduce

(*sendbuf,*recvbuf,count,datatype,(*sendbuf,*recvbuf,count,datatype, op,root, comm) op,root, comm) MPIMPI 缺省定义了如下的规约操作,用户可根据缺省定义了如下的规约操作,用户可根据自己的需要用自己的需要用 MPI_Op_createMPI_Op_create 函数创建新的函数创建新的规约操作:规约操作:

Page 34: MPI 分布内存并行 程序开发

MPI 规约操作 C语言数据类型 Fortran语言数据类型MPI_MAX 求最大值 integer, float integer, real, complexMPI_MIN 求最小值 integer, float integer, real, complexMPI_SUM 和 integer, float integer, real, complexMPI_PROD 乘积 integer, float integer, real, complexMPI_LAND 逻辑与 integer logicalMPI_BAND 按位与 integer, MPI_BYTE integer, MPI_BYTEMPI_LOR 逻辑或 integer logicalMPI_BOR 按位或 integer, MPI_BYTE integer, MPI_BYTEMPI_LXOR 逻辑异或 integer logicalMPI_BXOR 按位异或 integer, MPI_BYTE integer, MPI_BYTEMPI_MAXLOC 最大值和存储单元 float, double and long

doublereal,complex,doubleprecision

MPI_MINLOC 最小值和存储单元 float, double andlong double

real,complex,doubleprecision

Page 35: MPI 分布内存并行 程序开发

程序程序 99 、规约示例、规约示例 ##include “mpi.h”include “mpi.h” #include <stdio.h>#include <stdio.h> #include <math.h>#include <math.h> double f(double x);/*double f(double x);/* 定义函数定义函数 f(x) */f(x) */ {{ return(4.0/(1.0+x*x));return(4.0/(1.0+x*x)); } } int main (int argc,char * argv[])int main (int argc,char * argv[]) {{ int done =0,n,myid,numprocs,i;int done =0,n,myid,numprocs,i; double PI25DT=3.141592653589793238462643;double PI25DT=3.141592653589793238462643; double mypi,pi,h,sum,x;double mypi,pi,h,sum,x; double startwtime=0.0,endwtime;double startwtime=0.0,endwtime; int namelen;int namelen; char processor_name[MPI_MAXPROCESSOR_NAME]; char processor_name[MPI_MAXPROCESSOR_NAME];

Page 36: MPI 分布内存并行 程序开发

MPI_Init(&argc,&argv);MPI_Init(&argc,&argv); MPI_Comm_size(MPI_COMM_WORLD,&numprocs);MPI_Comm_size(MPI_COMM_WORLD,&numprocs); MPI_Comm_rank(MPI_COMM_WORLD,&myid);MPI_Comm_rank(MPI_COMM_WORLD,&myid); MPI_Get_processor_name(processor_name,&namelen);MPI_Get_processor_name(processor_name,&namelen); fprint(stdout,”Process %d of %d on % s\n”,myid,numprocs,fprint(stdout,”Process %d of %d on % s\n”,myid,numprocs, processor_name);processor_name); n=0;n=0; if (myid==0)if (myid==0) {{ printf(“Please give N=”);printf(“Please give N=”); scanf(&n);scanf(&n); startwtime=MPI_Wtime();startwtime=MPI_Wtime(); }} /*/*将将 nn 值广播出去值广播出去 */*/ MPI_Bcast(&n,1,MPI_INT,0,MPI_COMM_WORLD);MPI_Bcast(&n,1,MPI_INT,0,MPI_COMM_WORLD);

Page 37: MPI 分布内存并行 程序开发

h=1.0/(double) n;h=1.0/(double) n;

sum=0.0;sum=0.0; for(i=myid+1;i<=n;i+=numprocs)for(i=myid+1;i<=n;i+=numprocs) /* /* 每一个进程计算一部分矩形的面积,若进程总数每一个进程计算一部分矩形的面积,若进程总数 numprocsnumprocs 为为 44 ,, 将 将 0-10-1 区间划分为区间划分为 100100 个矩形,则各个进程分别计算矩形块个矩形,则各个进程分别计算矩形块 00 进程 进程 11 ,, 55 ,, 99 ,, 1313 ,……,,……, 9797 11 进程 进程 22 ,, 66 ,, 1010 ,, 1414 ,……,,……, 9898 22 进程 进程 33 ,, 77 ,, 1111 ,, 1515 ,……,,……, 9999 33 进程 进程 44 ,, 88 ,, 1212 ,, 1616 ,……,,……, 100 */100 */ {{ x=h*((double)i-0.5);x=h*((double)i-0.5); sum+=f(x);sum+=f(x); }} mypi=h*sum; /*mypi=h*sum; /* 各进程并行计算得到的部分和各进程并行计算得到的部分和 */*/

Page 38: MPI 分布内存并行 程序开发

/*/* 将部分和累加得到所有矩形的面积,该面积和即为近似将部分和累加得到所有矩形的面积,该面积和即为近似 PIPI 值值 */*/ MPI_Reduce(&mypi,&pi,1,MPI_DOUBLE,MPI_SUM,0,MPI_Reduce(&mypi,&pi,1,MPI_DOUBLE,MPI_SUM,0, MPI_COMM_WORLD);MPI_COMM_WORLD); if(myid==0)if(myid==0) {{ printf(“pi is approximately %.16f,Error is %.16f\n”,printf(“pi is approximately %.16f,Error is %.16f\n”, pi,fabs(pi-PI25DT));pi,fabs(pi-PI25DT)); endwtime=MPI_Wtime();endwtime=MPI_Wtime(); printf(“wall clock time=% f\n”,endwtime-startwtime);printf(“wall clock time=% f\n”,endwtime-startwtime); fflush(stdout);fflush(stdout); }} MPI_Finalize();MPI_Finalize(); } }

Page 39: MPI 分布内存并行 程序开发

MPI_Scan()MPI_Scan()

用来对分布在进程组上的数据执行前缀归约:用来对分布在进程组上的数据执行前缀归约: MPI_Scan (*sendbuf,*recvbuf,count,MPI_Scan (*sendbuf,*recvbuf,count, datatype,op,comm)datatype,op,comm)

Page 40: MPI 分布内存并行 程序开发
Page 41: MPI 分布内存并行 程序开发

进程数据缓冲区的变化情况

Page 42: MPI 分布内存并行 程序开发

群集函数的特点:群集函数的特点: 通讯因子中所有进程都要调用通讯因子中所有进程都要调用 除了除了 MPI_Barrier(),MPI_Barrier(), 其他函数使用类似标准阻其他函数使用类似标准阻塞的通信模式。一个进程一旦结束了它所参塞的通信模式。一个进程一旦结束了它所参与的群集操作就从群集例程中返回,并不保与的群集操作就从群集例程中返回,并不保证其他进程执行该群集例程已经完成。证其他进程执行该群集例程已经完成。 一个群集例程是不是同步操作取决于实现。一个群集例程是不是同步操作取决于实现。

Page 43: MPI 分布内存并行 程序开发

MPIMPI 并行程序的两种基本模式并行程序的两种基本模式 对等模式的对等模式的 MPIMPI 程序设计程序设计 主从模式的主从模式的 MPIMPI 程序设计程序设计

Page 44: MPI 分布内存并行 程序开发

一一 .. 对等模式的对等模式的 MPIMPI 程序设计程序设计1.1. 问题描述——问题描述—— JacobiJacobi 迭代迭代 JacobiJacobi 迭代是一种比较常见的迭代方法,迭代是一种比较常见的迭代方法,其核心部分可以用程序其核心部分可以用程序 11 来表示。简单的说来表示。简单的说,, JacobiJacobi 迭代得到的新值是原来旧值点相邻迭代得到的新值是原来旧值点相邻数值点的平均。数值点的平均。

JacobiJacobi 迭代的局部性很好,可以取得很高迭代的局部性很好,可以取得很高的并行性。将参加迭代的数据按块分割后,的并行性。将参加迭代的数据按块分割后,各块之间除了相邻的元素需要通信外,在各各块之间除了相邻的元素需要通信外,在各块的内部可以完全独立的并行计算。块的内部可以完全独立的并行计算。

Page 45: MPI 分布内存并行 程序开发

程序程序 10 10 串行表示的串行表示的 JacobiJacobi 迭代迭代…………REAL A(N+1,N+1),B(N+1,N+1)REAL A(N+1,N+1),B(N+1,N+1)…………DO K=1,STEPDO K=1,STEP DO J=1,NDO J=1,N DO I=1,NDO I=1,N B(I,J)=0.25*(A(I-1,J)+A(I+1,J)+A(I,J+1)+A(I,J-1))B(I,J)=0.25*(A(I-1,J)+A(I+1,J)+A(I,J+1)+A(I,J-1)) END DOEND DO END DOEND DO DO J=1,NDO J=1,N DO I=1,NDO I=1,N A(I,J)=B(I,J)A(I,J)=B(I,J) END DOEND DO END DOEND DOEND DOEND DO

Page 46: MPI 分布内存并行 程序开发

2.2.用用 MPIMPI 程序实现程序实现 JacobiJacobi 迭迭代代 为了并行求解,这里将参加迭代的数据按列进行分割,假设有 4个进程同时并行计算,数据的分割结果如图:

Page 47: MPI 分布内存并行 程序开发

假设需要迭代的数据是假设需要迭代的数据是 M*MM*M 的二维数组的二维数组 A(M,M),A(M,M),令令 M=4*N,M=4*N, 按按上上图进行数据划分,则分布在图进行数据划分,则分布在 44 个不同进程上的数据分别是: 个不同进程上的数据分别是: 进程进程 00:: A(M,1:N);A(M,1:N); 进程进程 11:: A(M,N+1:2*N);A(M,N+1:2*N); 进程进程 22:: A(M,2*N+1:3*N);A(M,2*N+1:3*N); 进程进程 33:: A(M,3*N+1:4*N).A(M,3*N+1:4*N). 由于在迭代过程中,边界点新值的计算需要相邻边界其他块的由于在迭代过程中,边界点新值的计算需要相邻边界其他块的数据,因此在每一个数据块的两侧各增加数据,因此在每一个数据块的两侧各增加 11列的数据空间,用于列的数据空间,用于存放从相邻数据块通信得到的数据。每个数据块的大小就从存放从相邻数据块通信得到的数据。每个数据块的大小就从 M*NM*N扩大到扩大到 M*(N+2)M*(N+2)。。 计算和通信过程是这样的:首先对数组赋初值,边界赋为计算和通信过程是这样的:首先对数组赋初值,边界赋为 88 ,,内部为内部为 00 。然后开始迭代,迭代之前,每个进程都需要从相邻的。然后开始迭代,迭代之前,每个进程都需要从相邻的进程得到数据块,同时也向相邻的进程提供数据块进程得到数据块,同时也向相邻的进程提供数据块 ((FORTRANFORTRAN 数数组在内存中是按列优先排列的组在内存中是按列优先排列的 )) 。。

Page 48: MPI 分布内存并行 程序开发

进程 0 进程 1 进程 2 进程3

发送 发送 发送

发送 发送 发送接收 接收 接收

接收接收接收

Page 49: MPI 分布内存并行 程序开发

程序程序 1111 、 、 并行的并行的 JacobiJacobi 迭代迭代program mainprogram maininclude ‘mpif.h’include ‘mpif.h’integer totalsize,mysize,stepsinteger totalsize,mysize,stepsParameter (totalsize=16)Parameter (totalsize=16)(( 定义全局数组的规模)定义全局数组的规模)parameter (mysize=totalsize/4,steps=10)parameter (mysize=totalsize/4,steps=10)

integer n,myid,numprocs,i,j,rcinteger n,myid,numprocs,i,j,rcReal a(totalsize,mysize+2),b(totalsize,mysize+2)Real a(totalsize,mysize+2),b(totalsize,mysize+2)Integer begin_col,end_col,ierrInteger begin_col,end_col,ierrInteger status(MPI_STATUS_SIZE)Integer status(MPI_STATUS_SIZE)

Page 50: MPI 分布内存并行 程序开发

call MPI_INIT(ierr)call MPI_INIT(ierr)call MPI_COMM_RANK(MPI_COMM_WORLD,myid,ierr)call MPI_COMM_RANK(MPI_COMM_WORLD,myid,ierr)call MPI_COMM_SIZE(MPI_COMM_WORLD,numprocs,ierr)call MPI_COMM_SIZE(MPI_COMM_WORLD,numprocs,ierr)print *,”Process”,myid,” of”,numprocs,” is alive”print *,”Process”,myid,” of”,numprocs,” is alive”

(( 数组初始化数组初始化 ))do j=1,mysize+2do j=1,mysize+2 do i=1,totalsizedo i=1,totalsize a(i,j)=0.0a(i,j)=0.0 end doend doend doend do

If (myid.eq.0) thenIf (myid.eq.0) then do i=1,totalsizedo i=1,totalsize a(i,2)=8.0a(i,2)=8.0 end doend doend ifend if

Page 51: MPI 分布内存并行 程序开发

If (myid.eq.3) thenIf (myid.eq.3) then do i=1,totalsizedo i=1,totalsize a(i,mysize+1)=8.0a(i,mysize+1)=8.0 end doend doend ifend if

If (myid.eq.3) thenIf (myid.eq.3) then do i=1,totalsizedo i=1,totalsize a(i,mysize+1)=8.0a(i,mysize+1)=8.0 end doend doend ifend if

do i=1,mysize+2do i=1,mysize+2 a(1,i)=8.0a(1,i)=8.0 a(totalsize,i)=8.0a(totalsize,i)=8.0end doend do

Page 52: MPI 分布内存并行 程序开发

(Jacobi(Jacobi 迭代部分迭代部分 ))do n=1,stepsdo n=1,steps(( 从右侧的邻居得到数据从右侧的邻居得到数据 ) ) if (myid.lt.3)thenif (myid.lt.3)then call MPI_RECV(a(1,mysize+2),totalsize,MPI_REAL,myid+1,call MPI_RECV(a(1,mysize+2),totalsize,MPI_REAL,myid+1, 10,MPI_COMM_WORLD,status,ierr) 10,MPI_COMM_WORLD,status,ierr) end ifend if

(( 向左侧的邻居发送数据向左侧的邻居发送数据 ) ) if (myid.gt.0)thenif (myid.gt.0)then call MPI_SEND(a(1,2),totalsize,MPI_REAL,myid-1,call MPI_SEND(a(1,2),totalsize,MPI_REAL,myid-1, 10,MPI_COMM_WORLD,ierr) 10,MPI_COMM_WORLD,ierr) end ifend if

Page 53: MPI 分布内存并行 程序开发

//// 向右侧的邻居发送数据 向右侧的邻居发送数据 if (myid.lt.3) thenif (myid.lt.3) then call MPI_SEND(a(1,mysize+1),totalsize,MPI_REAL,myid+1,call MPI_SEND(a(1,mysize+1),totalsize,MPI_REAL,myid+1, 10,MPI_COMM_WORLD,ierr) 10,MPI_COMM_WORLD,ierr) end ifend if

//// 从左侧的邻居接收数据 从左侧的邻居接收数据 if (myid.gt.0) thenif (myid.gt.0) then call MPI_RECV(a(1,1),totalsize,MPI_REAL,myid-1,call MPI_RECV(a(1,1),totalsize,MPI_REAL,myid-1, 10,MPI_COMM_WORLD,status,ierr) 10,MPI_COMM_WORLD,status,ierr) end ifend if

begin_col=2begin_col=2end_col=mysize+1end_col=mysize+1

Page 54: MPI 分布内存并行 程序开发

if (myid.eq.0) thenif (myid.eq.0) then begin_col=3begin_col=3 end ifend if

if (myid.eq.3) thenif (myid.eq.3) then end_col=mysizeend_col=mysize end ifend if

do j=begin_col,end_coldo j=begin_col,end_col do i=2,totalsize-1do i=2,totalsize-1 b(i,j)=0.25*(a(i,j+1)+a(i,j-1)+a(i+1,j)+a(i-1,j))b(i,j)=0.25*(a(i,j+1)+a(i,j-1)+a(i+1,j)+a(i-1,j)) end doend do end doend do

Page 55: MPI 分布内存并行 程序开发

do j=begin_col,end_coldo j=begin_col,end_col do i=2,totalsize-1do i=2,totalsize-1 a(i,j)=b(i,j)a(i,j)=b(i,j) end doend doend doend doend doend do

do i=2,totalsize-1do i=2,totalsize-1 print *,myid,(a(i,j),j=begin_col,end_col)print *,myid,(a(i,j),j=begin_col,end_col)end doend do

call MPI_FINALIZE(rc)call MPI_FINALIZE(rc)endend

Page 56: MPI 分布内存并行 程序开发

二二 .. 主从模式的主从模式的 MPIMPI 程序设计程序设计1.1. 问题描述——矩阵向量乘问题描述——矩阵向量乘 实现矩阵实现矩阵 C=A x B C=A x B 。。具体实现方法是:主进程将向量具体实现方法是:主进程将向量 BB 广广播给所有的从进程,然后将矩阵播给所有的从进程,然后将矩阵 AA 的各行依次发送给从进程,的各行依次发送给从进程,从进程计算一行和从进程计算一行和 BB 相乘的结果,然后将结果发送给主进程。相乘的结果,然后将结果发送给主进程。主进程循环向各个从进程发送一行的数据,直到将主进程循环向各个从进程发送一行的数据,直到将 AA 各行的各行的数据发送完毕。一旦主进程将数据发送完毕。一旦主进程将 AA 的各行发送完毕,则每收到的各行发送完毕,则每收到一个结果,就向相应的从进程发送结束标志,从进程接收到一个结果,就向相应的从进程发送结束标志,从进程接收到结束标志后退出执行。主进程收集完所有的结果后也结束。结束标志后退出执行。主进程收集完所有的结果后也结束。

Page 57: MPI 分布内存并行 程序开发

发送矩阵 A 的各行数据回收各行与 B 相乘的结果

计算 计算 计算 计算

主进程

从进程

送回结果

Page 58: MPI 分布内存并行 程序开发

程序程序 1122 、 矩阵向量乘、 矩阵向量乘program mainprogram maininclude “mpif.h”include “mpif.h”integer MAX_ROWS,MAX_COLS,rows,colsinteger MAX_ROWS,MAX_COLS,rows,colsparameter (MAX_ROWS=1000,MAX_COLS=1000)parameter (MAX_ROWS=1000,MAX_COLS=1000)double precision a(MAX_ROWS,MAX_COLS),b(MAX_COLS),c(MAX_COLS)double precision a(MAX_ROWS,MAX_COLS),b(MAX_COLS),c(MAX_COLS)double presicion buffer(MAX_COLS),ansdouble presicion buffer(MAX_COLS),ans

integer myid,master,numprocs,ierr,status(MPI_STATUS_SIZE)integer myid,master,numprocs,ierr,status(MPI_STATUS_SIZE)integer i,j,numsent,numrcvd,senderinteger i,j,numsent,numrcvd,senderinteger anstype,rowinteger anstype,row

Page 59: MPI 分布内存并行 程序开发

call MPI_INIT(ierr)call MPI_INIT(ierr)call MPI_COMM_RANK(MPI_COMM_WORLD,myid,ierr)call MPI_COMM_RANK(MPI_COMM_WORLD,myid,ierr)call MPI_COMM_SIZE(MPI_COMM_WORLD,numprocs,ierr)call MPI_COMM_SIZE(MPI_COMM_WORLD,numprocs,ierr)master=0master=0rows=100rows=100cols=100cols=100

If (myid.eq.master) thenIf (myid.eq.master) then (( 主进程对矩阵主进程对矩阵 AA和和 BB赋初值赋初值 )) do i=1,colsdo i=1,cols b(i)=1b(i)=1 do j=1,rowsdo j=1,rows a(I,j)=1a(I,j)=1 end doend do end doend do

Page 60: MPI 分布内存并行 程序开发

numsent=0numsent=0 numrcvd=0numrcvd=0 (( 将矩阵将矩阵 BB 发送给所有其他的从进程,通过下面的广播语句实现发送给所有其他的从进程,通过下面的广播语句实现 )) call MPI_BCAST(b,cols,MPI_DOUBLE_PRECISION,master,call MPI_BCAST(b,cols,MPI_DOUBLE_PRECISION,master,* MPI_COMM_WORLD,ierr)* MPI_COMM_WORLD,ierr)

(( 依次将矩阵依次将矩阵 AA 的各行发送给其他的的各行发送给其他的 numprocs-1numprocs-1 个从进程个从进程 )) do i=1,min(numprocs-1,rows)do i=1,min(numprocs-1,rows) do j=1,colsdo j=1,cols (( 将一行的数据取出来依次放到缓冲区中将一行的数据取出来依次放到缓冲区中 )) buffer(j)=a(i,j)buffer(j)=a(i,j) end doend do (( 将准备好的一行数据发送出去将准备好的一行数据发送出去 )) call MPI_SEND(buffer,cols,MPI_DOUBLE_PRECISION,i,i,call MPI_SEND(buffer,cols,MPI_DOUBLE_PRECISION,i,i,* MPI_COMM_WORLD,ierr)* MPI_COMM_WORLD,ierr) numsent=numsent+1numsent=numsent+1 end do end do

Page 61: MPI 分布内存并行 程序开发

(( 对所有的行,依次接收从进程对一行数据的计算结果对所有的行,依次接收从进程对一行数据的计算结果 )) do i=1,rowdo i=1,row call MPI_RECV(ans,1,MPI_DOUBLE_PRECISION,MPI_ANY_SOURCE,call MPI_RECV(ans,1,MPI_DOUBLE_PRECISION,MPI_ANY_SOURCE,* MPI_ANY_TAG,MPI_COMM_WORLD,status,ierr)* MPI_ANY_TAG,MPI_COMM_WORLD,status,ierr) sender=status(MPI_SOURCE)sender=status(MPI_SOURCE) anstype=status(MPI_TAG)anstype=status(MPI_TAG) (( 将该行数据赋给结果数组将该行数据赋给结果数组 CC 的相应单元的相应单元 )) c(anstype)=ansc(anstype)=ans (( 如果还有其他的行没有被计算,则继续发送如果还有其他的行没有被计算,则继续发送 )) if (numsent.lt.rows) thenif (numsent.lt.rows) then do j=1,colsdo j=1,cols (( 准备好新一行的数据准备好新一行的数据 )) buffer(j)=a(numsent+1,j)buffer(j)=a(numsent+1,j) end doend do (( 将该行数据发送出去将该行数据发送出去 )) call MPI_SEND(buffer,cols,MPI_DOUBLE_PRECISION,sender,call MPI_SEND(buffer,cols,MPI_DOUBLE_PRECISION,sender,* numsent+1,MPI_COMM_WORLD,ierr)* numsent+1,MPI_COMM_WORLD,ierr) numsent=numsent+1numsent=numsent+1

Page 62: MPI 分布内存并行 程序开发

elseelse (( 若所有行都已发送出去,则每接收一个消息则向相应的从进程发若所有行都已发送出去,则每接收一个消息则向相应的从进程发 送一个标志为送一个标志为 00 的空消息,终止该从进程的执行的空消息,终止该从进程的执行 )) call MPI_SEND(1.0,0,MPI_DOUBLE_PRECISION,sender,0,call MPI_SEND(1.0,0,MPI_DOUBLE_PRECISION,sender,0,* MPI_COMM_WORLD,ierr)* MPI_COMM_WORLD,ierr) end ifend if end doend do

elseelse (( 下面为从进程的执行步骤,首先是接收数组下面为从进程的执行步骤,首先是接收数组 B)B) call MPI_BCAST(b,cols,MPI_DOUBLE_PRECISION,master,call MPI_BCAST(b,cols,MPI_DOUBLE_PRECISION,master,* MPI_COMM_WORLD,ierr)* MPI_COMM_WORLD,ierr) (( 接收主进程发送过来的矩阵接收主进程发送过来的矩阵 AA 一行的数据一行的数据 )) call MPI_RECV(buffer,cols,MPI_DOUBLE_PRECISION,master,call MPI_RECV(buffer,cols,MPI_DOUBLE_PRECISION,master,* MPI_ANY_TAG,MPI_COMM_WORLD,status,ierr)* MPI_ANY_TAG,MPI_COMM_WORLD,status,ierr)

Page 63: MPI 分布内存并行 程序开发

(( 若接收到标志为若接收到标志为 00 的消息,则退出执行的消息,则退出执行 )) if (status(MPI_TAG).ne.0) thenif (status(MPI_TAG).ne.0) then row=status(MPI_TAGrow=status(MPI_TAG)) ans=0.0ans=0.0 do I=1,colsdo I=1,cols ans=ans+buffer(i)*b(j)ans=ans+buffer(i)*b(j) end doend do (( 计算一行的结果,并将结果发送给主进程计算一行的结果,并将结果发送给主进程 )) call MPI_SEND(ans,1,MPI_DOUBLE_PRECISION,master,row,call MPI_SEND(ans,1,MPI_DOUBLE_PRECISION,master,row, MPI_COMM_WORLD,ierr)MPI_COMM_WORLD,ierr) goto 90goto 90 end ifend ifend if end if call MPI_FINALIZE(ierr)call MPI_FINALIZE(ierr)endend