MPI教材
1,并行计算机的分类,根据指令数据分:单指令多数据、多指令多数据、单程序多数据、多程序多数据;根据存储方式分:共享内存、分布式内存、分布式共享内存。分布式结构中重要的一环是消息传递。
2,3种并行语言的实现方式:新语言、扩展语法、提供并行库。
3,MPI是一种基于消息传递模型的C/Fortran并行库。各种并行计算机一般都提供了对它的支持。
4,敲一遍Hello World:
#include "mpi.h"
#include <stdio.h>
#include <math.h>
void main(argc,argv)
int argc;
char *argv[];
{
int myid,numprocs;
int namelen;
char processor_name[MPI_MAX_PROCESSOR_NAME];
MPI_Init(&argc,&argv);
MPI_Comm_rank(MPI_COMM_WORLD,&myid);
MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
MPI_Get_processor_name(processor_name,&namelen);
fprintf(stderr, "Hello World! Process %d of %d on %s/n",
myid,numprocs,processor_name);
MPI_Finalize();
}
5,所有的MPI调用,以MPI_Aaaa_aaa的形式。不要自己写任何以MPI开头的函数。
6,MPI调用参数分为IN,OUT,INOUT三种。如果一个参数被一些并行执行的参数用作IN,而被另一些同时执行的进程用作OUT,这样的参数语法上也被记为INOUT。这个地方似乎非常恶心。
7,对于OUT,INOUT型的参数不可以使用参数别名。
8,MPI的调用,允许不同的数据类型使用相同的调用,即void *,有点C++中泛型的意味。
9,六个基本MPI调用:
int MPI_init(char *argc, char ***argv)
int MPI_Finalize(void)
int MPI_Comm_rank(MPI_Comm comm, int *rank) -- IN comm, OUT rank
提供当前进程在通信域中的进程标识号。
int MPI_Comm_size(MPI_Comm comm, int *size) -- IN comm, OUT size
提供通信域中的进程数量。
int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) - IN all
消息发送。
int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status * status)
IN count, datatype, source, tag, comm. OUT buf, status
消息接收。其中返回状态status需要先行分配空间,至少包括MPI_SOURCE, MPI_TAG, MPI_ERROR三个域。对它执行MPI_Get_count调用可以得到接收消息的长度。
10,所有这些调用的感觉比较像API。
11,在接收信息时可以接收来自同一通信域的任意源发出的消息,也可以接收任意tag的消息,也就是造成一种收发的不对称现象。可以给自身发消息,但要注意避免死锁。同样的,在多进程交互时要避免死锁。
12,MPI的两种开发模式:对等模式和主从模式。MPI程序一般是SPMD(单程序多数据)程序。
13,捆绑发送和接收程序可以用在接收与发送成对出现的场合,通信模块将合理优化其次序避免死锁。捆绑收发与普通收发之间可以通信。捆绑发送还可以只具有一个数据缓冲区,功能是先发送然后本地内容被接收到的内容所取代。
14,为了整体编程的方便,引入虚拟进程MPI_PRC_NULL。向它发送或从它接收会导致立刻成功结束,即什么都没做。
15,除了标准通信模式MPI_SEND之外,还有另外三种通信模式:
MPI_BSEND
缓存通信模式,可以在接收操作未执行的情况下成功返回,代价是延长通讯时间,缓冲区也不是总可以得到的。用户自行申请缓冲区,在“阻塞发送”的情况下,缓冲区直到无用后可以被收回。
MPI_SSEND
同步通信模式,可以在接收未准备好的情况下开始,但却必须在接收开始的情况下才能返回。
MPI_RSEND
就绪通信模式,要求在接收操作已经启动的情况下才能开始,否则会出错。关于这一模式的意义没有完全看明白。
16,关于mpi的安装和常见错误这两章暂且越过,等以后再回来看。
17,前面的通信方式是阻塞通信,实现计算与通信并行的方式是非阻塞通信。此时发送操作不必等待通信操作完成即可返回,这样,发送缓冲区就不能够立刻释放,需要引入“非阻塞通信完成对象”使程序员能够知道何时真正完成发送。对于接收方也有同样情况。
对于阻塞通信只需要一个调用函数即可以完成,但是对于非阻塞通信,一般需要两个调用函数。首先是非阻塞通信的启动,为了保证通信的完成还必须调用与该通信相联系的通信完成调用接口,通信完成,调用才真正将非阻塞通信完成。
针对某些通信是在一个循环中重复执行的情况,MPI又引入了重复非阻塞通信方式。
18,非阻塞通信的发送命令,对应4种通信方式而不同。只举标准通信为例:
MPI_ISEND ("I"表示Immediately,立即返回。)多一个参数request,通过对它的查询可以获得通信是否完成的消息。
对应的标准接收函数 MPI_IRECV。
用MPI_WAIT查询通信是否完成,它会在结束前释放掉对象request。MPI_TEST也有同样的功能,但是如果通信未完成它也会返回,而不像MPI_WAIT一直等待。应该说MPI_TEST更有用。
19,MPI_WAITANY, MPI_WAITSOME, WPI_WAITALL用来处理多个通信的情况。
MPI_CANCEL用来取消已调用的非阻塞通信。该调用立即返回。
一个通信操作是否被取消,可以通过调用测试函数MPI_TEST_CANCELLED来检查。
20,MPI提供MPI_PROBE和MPI_IPROBE调用允许程序员在不实际执行接收操作的情况下检查给定的消息是否到达。程序员可以根据返回的信息决定如何接收该消息,另外,程序员可以根据被检查消息的长度分配缓冲区大小。
MPI_IPROBE的source参数可以是MPI_ANY_SOURCE,tag参数可以是MPI_ANY_TAG,以便用户可以检查来自不确定的源source以及不确定的标识tag。
21,为了实现计算与通信的最大重叠,一个通用的原则就是“尽早开始通信,尽晚完成通信”。在开始通信和完成通信之间进行计算,这样通信启动得越早,完成得越晚,就有可能有更多的计算任务可以和通信重叠。
所以要先计算出要通信的数据,然后就开始发送,然后再计算其他数据。
22,重复非阻塞通信:如果一个通信会被重复执行,比如循环结构内的通信调用,MPI提供了特殊的实现方式,对这样的通信进行优化以降低不必要的通信开销。它将通信参数和MPI的内部对象建立固定的联系,然后通过该对象完成重复通信的任务。这样的通信方式在MPI中都是非阻塞通信。
重复非阻塞通信也一样有四种模式。
关于具体的使用在以后实际写测试程序的时候需要进行验证。
23,MPI支持组通信,先前的通信则可以被称作“点对点通信”。组通信需要一个特定组内的所有进程同时
参加通信。
组通信由哪些进程参加以及组通信的上下文都是由该组通信调用的通信域限定的,组通信调用可以和点对点通信共用一个通信域。MPI保证由组通信调用产生的消息不会和点对点调用产生的消息相混淆。
在组通信中不需要通信消息标志参数,如果将来的MPI新版本定义了非阻塞的组通信函数,也许那时就需要引入消息标志来防止组通信彼此之间造成的混淆了。汗这一段。
组通信一般实现三个功能:通信,同步和计算。通信功能主要完成组内数据的传输,而同步功能实现组内所有进程在特定的地点在执行进度上取得一致。计算功能稍微复杂一点,要对给定的数据完成一定的操作。
24,组通信一般用来多对一、一对多或者多对多。
同步的作用是当进程完成同步调用后,可以保证所有的进程都已执行了同步点前面的操作。
从效果上可以认为MPI组通信的计算功能是分三步实现的。首先是通信的功能即消息根据要求发送到目的进程,目的进程也已经接收到了各自所需要的消息,然后是对消息的处理即计算部分。MPI组通信有计算功能的调用都指定了计算操作,用给定的计算操作对接收到的数据进行处理。最后一步是将处理结果放入指定的接收缓冲区。
25,一对多操作,发送的函数调用“广播”:MPI_BCAST。它完成从一个标识为root的进程将一条消息
广播发送到组内的所有其它的进程,同时也包括它本身在内。
多对一操作,函数调用“收集”:MPI_GATHER。每个进程,包括根进程本身,将其发送缓冲区中的消息发送到根进程,根进程根据发送进程的进程标识的序号,即进程的rank值,将它们各自的消息依次存放到自已的消息缓冲区中。
和广播调用不同的是,广播出去的数据都是相同的,但对于收集操作,虽然从各个进程收集到的数据的个数必须相同,但从各个进程收集到的数据一般是互不相同的。其结果就象一个进程组中的N个进程,包括根进程在内,都执行了一个发送调用,同时根进程执行了N次接收调用。
MPI_GATHERV和MPI_GATHER的功能类似,但是它可以从不同的进程接收不同数量的数据。为此接收数据元素的个数recvcounts是一个数组,用于指明从不同的进程接收的数据元素的个数。但是发送和接收的个数必须一致。除此之外,它还为每一个接收消息在接收缓冲区的位置提供了一个位置偏移displs数组,用户可以将接收的数据存放到根进程消息缓冲区的任意位置。
26,一对多操作,散发:MPI_SCATTER。它是一对多的组通信调用但是和广播不同,ROOT向各个进程发送的数据可以是不同的。MPI_SCATTER和MPI_GATHER的效果正好相反,两者互为逆操作。
正如MPI_SCATTER是MPI_GATHER的逆操作一样,也有一个MPI_SCATTERV是MPI_GATHERV的逆操作。MPI_SCATTERV对MPI_SCATTER的功能进行了扩展,它允许ROOT向各个进程发送个数不等的数据。(注意,基本的散发操作只是允许内容不同,个数必须是一样的)。
27,多对多操作,组收集:MPI_ALLGATHER。MPI_ALLGATHER相当于每一个进程都作为ROOT执行了一次MPI_GATHER调用,即每一个进程都收集到了其它所有进程的数据。
MPI_ALLGATHERV也是所有的进程都将接收结果,而不是只有根进程接收结果。从每个进程发送的第j块数据将被每个进程接收,然后存放在各个进程接收消息缓冲区。recvbuf的第j块进程j的sendcount和sendtype的类型必须和其他所有进程的recvcounts[j]和recvtype相同。
28,多对多操作,全互换:MPI_ALLTOALL。它是组内进程之间完全的消息交换。其中每一个进程都相其它所有的进程发送消息,同时每一个进程都从其它所有的进程接收消息。
MPI_ALLTOALL散发给不同进程的消息是不同的,因此它的发送缓冲区也是一个数组MPI_ALLTOALL的每个进程可以向每个接收者发送数目不同的数据。
正如MPI_ALLGATHERV 和MPI_ALLGATHER 的关系一样,有MPI_ALLTOALLV。它在MPI_ALLTOALL的基础上进一步增加了灵活性,它可以由sdispls指定待发送数据的位置,在接收方则由rdispls指定接收的数据存放在缓冲区的偏移量。
MPI_ALLTOALL和MPI_ALLTOALLV可以实现n次独立的点对点通信但也有限制:
1) 所有数据必须是同一类型
2)所有的消息必须按顺序进行散发和收集
29,同步函数调用:MPI_BARRIER。阻塞所有的调用者直到所有的组成员都调用了它,直到这时各个进程中这个调用才可以返回。
30,归约,MPI_REDUCE。将组内每个进程输入缓冲区中的数据按给定的操作op进行运算并将其
结果返回到序列号为root的进程的输出缓冲区中。
输入缓冲区由参数sendbuf count和datatype定义,输出缓冲区由参数recvbuf count和datatype定义,要求两者的元素数目和类型都必须相同,因为所有组成员都用同样的参数count datatype op root和comm来调用。
操作op始终被认为是可结合的,并且所有MPI定义的操作被认为是可交换的,用户自定义的操作被认为是可结合的,但可以不是可交换的。
看起来很费解,看一下mpi提供的一些已定义好的op能够更好的理解,实际上“计算”就是这种意义:
MPI_MAX 最大值
MPI_MIN 最小值
MPI_SUM 求和
MPI_PROD 求积
MPI_LAND 逻辑与
MPI_BAND 按位与
MPI_LOR 逻辑或
MPI_BOR 按位或
MPI_LXOR 逻辑异或
MPI_BXOR 按位异或
MPI_MAXLOC 最大值且相应位置
MPI_MINLOC 最小值且相应位置