[关闭]
@Purpose 2017-03-28T11:39:46.000000Z 字数 8557 阅读 5824

MPI学习笔记

Linux笔记


MP函数约定

C中的MPI函数约定
1. 必须包含mpi.h
2. MPI函数返回出错代码或MPI_SUCCESS成功标志
3. MPI-前缀,且只有MPI和MPI_标志后的第一个字母大写,其余小写


6个基本的MPI函数

MPI初始化

  1. int MPI_iNIT(int *argc, char **argv[])
  1. MPI_INIT是MPI程序的第一个调用,它完成MPI程序的所有初始化工作。所有的MPI程序的第一条可执行语句都是这条语句
  2. 启动MPI环境,标志并行代码的开始
  3. 并行代码之前,第一个mpi函数(除MPI_Initialize()外).
  4. 要求main必须带参数运行,否则出错.

MPI结束

  1. int MPI_Finalize
  1. MPI_FINALIZE是MPI程序的最后一个调用,它结束MPI程序的运行,它是MPI程序的最后一条可执行语句,否则程序的运行结果是不可预知的。
  2. 标志并行代码的结束,结束除主进程外其它进程.
  3. 之后串行代码仍可在主进程(rank = 0)上运行(如果必须)

获取进程

  1. /*用MPI_Comm_size 获得进程个数 p*/
  2. int MPI_Comm_size(MPI_Comm comm, int *size)
  3. /*用MPI_Comm_rank 获得进程的一个叫rank的值,
  4. 该rank值为0到p-1间的整数,相当于进程的ID*/
  5. int MPI_Comm_rank(MPI_Comm comm, int *rank)

通信子(通信空间)

  1. MPI_COMM_WORLD
  1. 一个通信空间是一个进程组和一个上下文的组合.上下文可看作为组的超级标签,用于区分不同的通信子.
  2. 在执行函数MPI_Init之后,一个MPI程序的所有进程形成一个缺省的组,这个组的通信子即被写作MPI_COMM_WORLD.
  3. 该参数是MPI通信操作函数中必不可少的参数,用于限定参加通信的进程的范围.

消息传递

  1. int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm);
  2. /**消息发送
  3. *IN buf 发送缓冲区的起始地址
  4. *IN count 要发送信息的元素个数
  5. *IN datatype 发送信息的数据类型
  6. *IN dest 目标进程的rank值
  7. *IN tag 消息标签
  8. *IN comm 通信子
  9. */
  10. int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status);
  11. /**消息接收
  12. *OUT buf 发送缓冲区的起始地址
  13. *IN count 要发送信息的元素个数
  14. *IN datatype 发送信息的数据类型
  15. *IN dest 目标进程的rank值
  16. *IN tag 消息标签
  17. *IN comm 通信子
  18. *OUT status status对象,包含实际接收到的消息的有关信息
  19. */

概念简析

group是MPI一个很重要的概念,一台电脑可以属于多个group,group的正真强大体现在可以随时随地的组合任意group,然后利用gourp内,和group间的communicator,可以很容易实现复杂科学计算的中间过程,比如奇数rank一个group,偶数另一个group,或者拓扑结构的group,这样可以解决很多复杂问题,另外MPI还有一个默认的全局的group,他就是comm world,一般简单的应用有了这一个group已经足够了。

rank就是任意group内的一个计算单元,利用rank我们可以很轻松的实现client server的架构,比如rank=0是server其他就是client。

communicator就是各种通信,比如一对一,一对多,多对一,其中多往往代表着一个group, 在传输过程中tag还是很有用的可以用来区别不同的任务类型,一般都是先解析tag,然后再解析具体的数据内容, 这里要有一个信封和信内容的差别的概念,理解了这样的差别,可以很好的扩展程序。

type是MPI的自定义类型,由于通常编程的时候常用struct 数组 和离散的变量,这些东西不能直接进行通信, 然后MPI同样有一套这样的定义,我们可以转化成MPI的格式,这样就可以很自由的通信了。

Pack就是把离散的数据打包起来,方便传送,其实这个作用和type很类似,如果你不想很麻烦的定义type直接打包发送

spawn是区分MPI一代和二代的一个重要的标志,有了spawn,就可以在运行过程中自动的改变process的数量,可能复杂的软件才有这样的需求。

window远程的控制同一个文件,只有在网络条件很好的时候用这个才有意义,否则会让软件效率变得很糟糕。


Point to Point(点到点通信)

术语解析

Blocking(阻塞) :一个例程须等待操作完成才返回,返回后用户可以重新使用调用中所占用的资源.
Non-blocking(非阻塞):一个例程不必等待操作完成便可返回,但这并不意味着所占用的资源可被重用.
Local(本地):不通信.
Non-local(非本地):通信.


消息标识

  • 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,用于标识该通讯空间.
  • 数据类型
    • 异构计算:数据转换.
    • 派生数据类型:结构或数组散元传送

消息匹配

接收buffer必须至少可以容纳count个由datatype参数指明类型的数据. 如果接收buf太小, 将导致溢出、出错

  1. 参数匹配 dest,tag,comm/ source,tag,comm
  2. Source == MPI_ANY_SOURCE:接收任意处理器来的数据(任意消息来源).
  3. Tag == MPI_ANY_TAG:匹配任意tag值的消息(任意tag消息).

    在阻塞式消息传送中不允许Source==Dest,否则会导致deadlock.
    消息传送被限制在同一个communicator.
    在send函数中必须指定唯一的接收者(Push/pull通讯机制).


status参数

  1. int MPI_Get_count(MPI_Status status,MPI_Datatype datatype,int* count)
  2. /**
  3. *IN status 接收操作的返回值.
  4. *IN datatype 接收缓冲区中元素的数据类型.
  5. *OUT count 接收消息中的元素个数
  6. */

Greeting例子

  1. #include <stdio.h>
  2. #include "mpi.h"
  3. main(int argc,char*argv[])
  4. {
  5. int numprocs; /*进程数,该变量为各处理器中的同名变量, 存储是分布的 **/
  6. int myid; /*我的进程ID,存储也是分布的*/
  7. int source;
  8. MPI_Status status; /*消息接收状态变量,存储也是分布的*/
  9. char message[100]; /*消息buffer,存储也是分布的*/
  10. /*初始化MPI*/
  11. MPI_Init(&argc,&argv);
  12. /*该函数被各进程各调用一次,得到自己的进程rank值*/
  13. MPI_Comm_rank(MPI_COMM_WORLD,&myid);
  14. /*该函数被各进程各调用一次,得到进程数*/
  15. MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
  16. if(myid !=0){
  17. /*建立消息*/
  18. sprintf(message, "Greetings from process %d!",myid);
  19. /* 发送长度取strlen(message)+1,使\0也一同发送出去*/
  20. MPI_Send(message, strlen(message)+1, MPI_CHAR, 0, 99, MPI_COMM_WORLD);
  21. }else{
  22. for(source = 1; source < numprocs; source++){
  23. MPI_Recv(message, 100, MPI_CHAR, source, 99, MPI_COMM_WORLD, &status);
  24. printf("%s\n",message);
  25. }/*end for*/
  26. }/*end if*/
  27. /*关闭MPI,标志并行代码段的结束*/
  28. MPI_Finalize();
  29. }/*end main*/

MPI_Sendrecv函数

  1. /*函数原型*/
  2. int MPI_Sendrecv(
  3. void *sendbuf,
  4. int sendcount,
  5. MPI_Datatype sendtype,
  6. int dest,
  7. int sendtag,
  8. void *recvbuf,
  9. int recvcount,
  10. MPI_Datatype recvtype,
  11. int source,
  12. int recvtag,
  13. MPI_Comm comm,
  14. MPI_Status *status)
  15. /*用法*/
  16. .../*略*/
  17. int a,b;
  18. .../*略*/
  19. MPI_Status status;
  20. int dest = (rank+1)%p;
  21. int source = (rank + p -1)%p; /*p为进程个数*/
  22. /*该函数被每一进程执行一次*/
  23. MPI_Sendrecv( &a, 1, MPI_INT, dest, 99, &b 1,MPI_INT, source, 99, MPI_COMM_WORLD, &status);

空进程

rank = MPI_PROC_NULL的进程称为空进程
使用空进程的通信不做任何操作
向MPI_PROC_NULL发送的操作总是成功并立即返回.
从MPI_PROC_NULL接收的操作总是成功并立即返回,且接收缓冲区内容为随机数

  1. /*status*/
  2. status.MPI_SOURCE = MPI_PROC_NULL
  3. status.MPI_TAG = MPI_ANY_TAG
  4. MPI_Get_count(&status,MPI_Datatype datatype, &count) =>count = 0
  5. /*空进程应用示意*/
  6. MPI_Status status;
  7. int dest = (rank+1) % p;
  8. int source = (rank + p-1) % p;
  9. if(source == p-1)
  10. source = MPI_PROC_NULL;
  11. if(dest == 0)
  12. dest = MPI_PROC_NULL;
  13. MPI_Sendrecv( &a, 1, MPI_INT, dest, 99, &b 1, MPI_INT, source, 99, MPI_COMM_WORLD, &status);

通信模式

阻塞通信模式

发送方体现(send语句)
阻塞通信中接收语句相同,MPI_Recv
按发送方式的不同,消息或直接被copy到接收者的buffer中或被拷贝到系统buffer中。

  • 标准模式Standard
    • 最常用的发送方法MPI_Send()
  • B:缓冲模式Buffer
    • 发送到系统缓冲区
    • MPI_Bsend()
  • S:同步模式Synchronous
    • 任意发出,不需系统缓冲区
    • MPI_Ssend()
  • R:就绪模式Ready
    • 就绪发出,不需系统缓冲区
    • MPI_Rsend()

标准模式Standard

直接送信或者通过邮局送信

  • 由MPI决定是否缓冲信息
    1. 没有足够的系统缓冲区时或出于性能的考虑,MPI可能进行直接拷贝:仅当相应的接收开始后,发送语句才能返回
    2. MPI缓冲消息:发送语句地相应的接收语句完成前返回
  • 发送的结束 == 消息已从发送方发出,而不是滞留在发送方的系统缓冲区中
  • 非本地的:发送操作的成功与否依赖于接收操作
  • 最常用的发送方式

缓冲模式Buffer

通过邮局送信(应用系统缓冲区)

  • 前提: : 用户显示地指定用于缓冲消息的系统缓冲区
    • MPI_Buffer_attach(*buffer, *size)
  • 发送是本地的: 完成不依赖于与其匹配的接收操作。发送的结束仅表明消息进入系统的缓冲区中,发送方缓冲区可以重用,而对接收方的情况并不知道
  • 缓冲模式在相匹配的接收未开始的情况下,总是将送出的消息放在缓冲区内,这样发送者可以很快地继续计算,然后由系统处理放在缓冲区中的消息
  • 占用内存,一次内存拷贝。
  • 其函数调用形式为:MPI_Bsend(…)B代表缓冲

同步模式Synchronous

握手后才送出名片(遵从three-way协议)

  • 本质特征:收方接收该消息的缓冲区已准备好,不需要附加的系统缓冲区
  • 任意发出:发送请求可以不依赖于收方的匹配的接收请求而任意发出
  • 成功结束:仅当收方已发出接收该消息的请求后才成功返回,否则将阻塞。意味着
    • 发送方缓冲区可以重用
    • 收方已发出接收请求
  • 是非本地的
  • 其函数调用形式为:MPI_Ssend(…)S代表同步

就绪模式Ready

有客户请求,才提供服务

  • 发送请求仅当有匹配的接收后才能发出,否则出错。在就绪模式下,系统默认与其相匹配的接收已经调用。接收必须先于发送
  • 它不可以不依赖于接收方的匹配的接收请求而任意发出
  • 其函数调用形式为:MPI_Rsend(…)。R代表准备

阻塞与非阻塞的差别

  • 用户发送缓冲区的重用
    • 非阻塞的发送:仅当调用了有关结束该发送的语句后才能重用发送缓冲区,否则将导致错误;对于接收方,与此相同,仅当确认该接收请求已完成后才能使用。所以对于非阻塞操作,要先调用等待MPI_Wait()或测试MPI_Test()函数来结束或判断该请求,然后再向缓冲区中写入新内容或读取新内容
  • 阻塞发送将发生阻塞,直到通讯完成
  • 非阻塞可将通讯交由后台处理,通信与计算可重叠
  • 发送语句的前缀由MPI_改为MPI_I, I:immediate:
    • 标准模式:MPI_Send(…)->MPI_Isend(…)
    • Buffer模式:MPI_Bsend(…)->MPI_Ibsend(…)
    • Synchronous模式:MPI_Ssend(…)->MPI_Issend(…)
    • Ready模式:MPI_Rsend(…)->MPI_Irsend(…)

非阻塞发送与接收

  1. int MPI_Isend(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request)
  2. /**
  3. ` IN buf 发送缓冲区的起始地址
  4. ` IN count 发送缓冲区的大小(发送元素个数)
  5. ` IN datatype 发送缓冲区数据的数据类型
  6. ` IN dest 目的进程的秩
  7. ` IN tag 消息标签
  8. ` IN comm 通信空间/通信子
  9. ` OUT request 非阻塞通信完成对象(句柄)
  10. */
  11. /*MPI_Ibsend/MPI_Issend/MPI_Irsend:非阻塞缓冲模式/非阻塞同步模式/非阻塞就绪模式*/
  12. int MPI_Irecv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request* request)

通信的完成

常用于非阻塞通信

  1. ####MPI_Wait()
  2. int MPI_Wait(MPI_Request* request, MPI_Status * status);
  3. /*当request标识的通信结束后,MPI_Wait()才返回。如果通信是非阻塞的,返回时request = MPI_REQUEST_NULL;函数调用是非本地的;*/
  4. MPI_Request request;
  5. MPI_Status status;
  6. int x, y;
  7. if(rank == 0){
  8. MPI_Isend(&x, 1, MPI_INT, 1, 99, comm, &request)
  9. MPI_Wait(&request, &status);
  10. }else{
  11. MPI_Irecv(&y, 1, MPI_INT, 0, 99, comm, &request)
  12. MPI_Wait(&request, &status);
  13. }
  14. ####MPI_Test
  15. int MPI_Test(MPI_Request *request,int *flag, MPI_Status *status);
  16. MPI_Request request;
  17. MPI_Status status;
  18. int x, y, flag;
  19. if(rank == 0){
  20. MPI_Isend(&x, 1, MPI_INT, 1, 99, comm, &request)
  21. while(!flag)
  22. MPI_Test(&request, &flag, &status);
  23. }else{
  24. MPI_Irecv(&y, 1, MPI_INT, 0, 99, comm, &request)
  25. while(!flag)
  26. MPI_Test(&request, &flag, &status);
  27. }

消息探测

适用于阻塞与非阻塞
MPI_Probe()和MPI_Iprobe()函数探测接收消息的内容。用户根据探测到的消息内容决定如何接收这些消息,如根据消息大小分配缓冲区等。前者为阻塞方式,即只有探测到匹配的消息才返回;后者为非阻塞,即无论探测到与否均立即返回.

  1. int MPI_Probe(int source, int tag, MPI_Comm comm, MPI_Status* status)
  2. int MPI_Iprobe(int source, int tag, MPI_Comm comm, int*flag, MPI_Status* status)
  3. /**
  4. `IN source 数据源的rank,可以是MPI_ANY_SOURCE
  5. `IN tag 数据标签,可以是MPI_ANY_TAG
  6. `IN comm 通信空间/通信子
  7. `OUT flag 布尔值,表示探测到与否(只用于非阻塞方式)
  8. `OUT status status对象,包含探测到消息的内容
  9. */
  10. int x;
  11. float y;
  12. MPI_Comm_rank(comm, &rank);
  13. if(rank == 0){ /*0->2发送一int型数*/
  14. MPI_Send(100, 1, MPI_INT, 2, 99, comm);
  15. }else if(rank == 1){ /*1->2发送一float型数*/
  16. MPI_Send(100.0, 1, MPI_FLOAT, 2, 99, comm);
  17. }else{ /* 根进程接收 */
  18. for(int i=0; i<2; i++) {
  19. MPI_Probe(MPI_ANY_SOURCE, 0, comm, &status);/*Blocking*/
  20. if (status.MPI_SOURCE == 0)
  21. MPI_Recv(&x, 1, MPI_INT, 0, 99, &status);
  22. else if(status.MPI_SOURCE == 1)
  23. MPI_Recv(&y, 1, MPI_FLOAT, 0, 99, &status);
  24. }
  25. }

MPI程序的编译

基本执行方法

mpicc来进行编译的时候,和用gcc编译一样

  1. /*编译*/
  2. mpicc -c foo.c
  3. mpicc -o foo foo.c
  4. /*运行*/
  5. mpirun -np 4 foo

通过配置文件执行

我们可以设定配置文件来表达各个进程的分布设置

  1. /*运行方式*/
  2. mpirun p4pg <pgfile> <program>
  3. /*<pgfile>是配置文件*/
  4. /*配置文件的格式为
  5. *<机器名> <进程数> <程序名>
  6. *<机器名> <进程数> <程序名>
  7. *<机器名> <进程数> <程序名>
  8. **/
  9. node0 0 /public0/czn/mpi/cpi
  10. node1 1 /public0/czn/mpi/cpi
  11. node2 1 /public0/czn/mpi/cpi
  12. /*第一行的0并不表示在node0上没有进程,这里的0特指在node0上启动MPI程序*/

这种方式允许可执行程序由不同的名字和不同的路径组成


完整的MPI运行方式

  1. mpirun np <number of processor> <programname and argument>
  2. mpirun [mpirun_options] <program> [options…]
  3. /*详细参数信息执行mpirun -help*/
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注