MPI学习笔记

MPI学习笔记


MP函数约定

C中的MPI函数约定

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

6个基本的MPI函数

MPI初始化

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

MPI结束

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

获取进程

/*用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)

通信子(通信空间)

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

消息传递

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 通信子
*/

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 dest 目标进程的rank值
*IN tag 消息标签
*IN comm 通信子
*OUT status status对象,包含实际接收到的消息的有关信息
*/

概念简析

***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参数

  • 当使用MPI_ANY_SOURCE或/和MPI_ANY_TAG接收消息时如何确定消息的来源source 和 tag值呢?
    在C中,可以用status.MPI_SOURCE, status.MPI_TAG

  • Status还可用于返回实际接收到消息的长度

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


Greeting例子

#include <stdio.h>
#include "mpi.h"
main(int argc,char*argv[])
{
    int numprocs;      /*进程数,该变量为各处理器中的同名变量, 存储是分布的 **/
    int myid;          /*我的进程ID,存储也是分布的*/
    int source;        
    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);

    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{
         for(source = 1; source < numprocs; source++){
            MPI_Recv(message, 100, MPI_CHAR, source, 99, MPI_COMM_WORLD, &status);
            printf("%s\n",message);
        }/*end for*/
    }/*end if*/

    /*关闭MPI,标志并行代码段的结束*/
    MPI_Finalize();
}/*end main*/

MPI_Sendrecv函数

/*函数原型*/
int MPI_Sendrecv(
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)

/*用法*/

.../*略*/
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);


空进程

rank = MPI_PROC_NULL的进程称为空进程
使用空进程的通信不做任何操作
向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

/*空进程应用示意*/
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);


通信模式

阻塞通信模式

发送方体现(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(…)

非阻塞发送与接收

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 非阻塞通信完成对象(句柄)
*/

/*MPI_Ibsend/MPI_Issend/MPI_Irsend:非阻塞缓冲模式/非阻塞同步模式/非阻塞就绪模式*/

int MPI_Irecv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request* request)

通信的完成

常用于非阻塞通信

  • 发送的完成: 代表发送缓冲区中的数据已送出,发送缓冲区可以重用。它并不代表数据已被接收方接收。数据有可能被缓冲;
    • 同步模式:发送完成==接收方已初始化接收,数据将被接收方接收
  • 接收的完成:代表数据已经写入接收缓冲区。接收者可访问接收缓冲区,status对象已被释放。它并不代表相应的发送操作已结束。
  • 通过MPI_Wait()和MPI_Test()来判断通信是否已经完成;
####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)
    …
    MPI_Wait(&request, &status);
}

####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)
    while(!flag)
        MPI_Test(&request, &flag, &status);
}


消息探测

适用于阻塞与非阻塞
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对象,包含探测到消息的内容
*/

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);
    }
}

MPI程序的编译

基本执行方法

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

/*编译*/
mpicc -c foo.c
mpicc -o foo foo.c
/*运行*/
mpirun -np 4 foo

通过配置文件执行

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

/*运行方式*/
mpirun –p4pg <pgfile> <program>
/*<pgfile>是配置文件*/

/*配置文件的格式为
*<机器名> <进程数> <程序名>
*<机器名> <进程数> <程序名>
*<机器名> <进程数> <程序名>
**/
node0 0 /public0/czn/mpi/cpi 
node1 1 /public0/czn/mpi/cpi
node2 1 /public0/czn/mpi/cpi
/*第一行的0并不表示在node0上没有进程,这里的0特指在node0上启动MPI程序*/

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


完整的MPI运行方式

mpirun –np <number of processor> <programname and argument>
mpirun [mpirun_options] <program> [options…]
/*详细参数信息执行mpirun -help*/
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值