@Purpose
2017-03-28T11:39:46.000000Z
字数 8557
阅读 5824
Linux笔记
C中的MPI函数约定
1. 必须包含mpi.h
2. MPI函数返回出错代码或MPI_SUCCESS成功标志
3. MPI-前缀,且只有MPI和MPI_标志后的第一个字母大写,其余小写
int MPI_iNIT(int *argc, char **argv[])
int MPI_Finalize
/*用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
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远程的控制同一个文件,只有在网络条件很好的时候用这个才有意义,否则会让软件效率变得很糟糕。
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太小, 将导致溢出、出错
Tag == MPI_ANY_TAG:匹配任意tag值的消息(任意tag消息).
在阻塞式消息传送中不允许Source==Dest,否则会导致deadlock.
消息传送被限制在同一个communicator.
在send函数中必须指定唯一的接收者(Push/pull通讯机制).
当使用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 接收消息中的元素个数
*/
#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*/
/*函数原型*/
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()
直接送信或者通过邮局送信
- 由MPI决定是否缓冲信息
- 没有足够的系统缓冲区时或出于性能的考虑,MPI可能进行直接拷贝:仅当相应的接收开始后,发送语句才能返回
- MPI缓冲消息:发送语句地相应的接收语句完成前返回
- 发送的结束 == 消息已从发送方发出,而不是滞留在发送方的系统缓冲区中
- 非本地的:发送操作的成功与否依赖于接收操作
- 最常用的发送方式
通过邮局送信(应用系统缓冲区)
- 前提: : 用户显示地指定用于缓冲消息的系统缓冲区
MPI_Buffer_attach(*buffer, *size)
- 发送是本地的: 完成不依赖于与其匹配的接收操作。发送的结束仅表明消息进入系统的缓冲区中,发送方缓冲区可以重用,而对接收方的情况并不知道
- 缓冲模式在相匹配的接收未开始的情况下,总是将送出的消息放在缓冲区内,这样发送者可以很快地继续计算,然后由系统处理放在缓冲区中的消息
- 占用内存,一次内存拷贝。
- 其函数调用形式为:
MPI_Bsend(…)
B代表缓冲
握手后才送出名片(遵从three-way协议)
- 本质特征:收方接收该消息的缓冲区已准备好,不需要附加的系统缓冲区
- 任意发出:发送请求可以不依赖于收方的匹配的接收请求而任意发出
- 成功结束:仅当收方已发出接收该消息的请求后才成功返回,否则将阻塞。意味着
- 发送方缓冲区可以重用
- 收方已发出接收请求
- 是非本地的
- 其函数调用形式为:
MPI_Ssend(…)
S代表同步
有客户请求,才提供服务
- 发送请求仅当有匹配的接收后才能发出,否则出错。在就绪模式下,系统默认与其相匹配的接收已经调用。接收必须先于发送
- 它不可以不依赖于接收方的匹配的接收请求而任意发出
- 其函数调用形式为:
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)
常用于非阻塞通信
####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);
}
}
用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程序*/
这种方式允许可执行程序由不同的名字和不同的路径组成
mpirun –np <number of processor> <programname and argument>
mpirun [mpirun_options] <program> [options…]
/*详细参数信息执行mpirun -help*/