MPI
MPI_Init(&argc, &argv) 与 MPI_Finalize()
这其实就是一个程序的框架,我们这么来用,其中MPI_Init会在用户启动程序的时候,定义由用户启动的所有进程所组成的通信子MPI_COMM_WORLD
#include <mpi.h>
//...
int main(int argc, char* argv[]){
//...
MPI_Init(&argc, &argv); //初始化
//这里有放我们的mpi代码
MPI_Finalize(); //退出mpi系统
//...
}
MPI_Abort
//异常终止MPI程序
int MPI_Abort(
MPI_Comm comm, //通信子
int errorcode //作为进程的退出码返回给系统
)
MPI_Comm_size
//返回进程数
int MPI_Comm_size(
MPI_Comm comm, //通信子
int* comm_sz_p //返回值,表示进程数
)
MPI_Comm_rank
//返回正在调用的进程再通信子中的进程号
int MPI_Comm_rank(
MPI_Comm comm, //通信子
int* my_rank_p //返回值,表示进程号
)
MPI_Send
//发送消息
int MPI_Send(
void* msg_buf_p, //指向消息内容的指针,比如a[10]的话就是a
int msg_size, //消息长度,比如a[10]的话就是11
MPI_Datatype msg_type, //MPI数据类型
int dest, //指定要接收消息的进程的进程号
int tag, //区分消息用,比如同一种消息0代表用于打印,1代表用于计算
MPI_Comm communicator //通信子,如MPI_COMM_WORLD
)
MPI_Recv
0号进程一般用于接收数据
//接收消息
int MPI_Recv(
void* msg_buf_p, //指向消息内容的指针,比如a[10]的话就是a
int buf_size, //消息长度,比如a[10]的话就是11
MPI_Datatype buf_type, //MPI数据类型
int source, //指定接收的消息从哪个进程发送过来
int tag, //区分消息用,比如同一种消息0代表用于打印,1代表用于计算
MPI_Comm communicator, //与发送相匹配的通信子,如MPI_COMM_WORLD
MPI_Status* status_p //大多数情况下不使用,赋予其MPI_STATUS_IGNORE即可
)
MPI_Status
typedef struct{
int count; //不能直接被访问
int cancelled; //不能直接被访问
int MPI_SOURCE; //消息源地址
int MPI_TAG; //消息标号
int MPI_ERROR; //接收操作的错误码
}MPI_Status;
MPI_Get_processor_name
//获取处理器名称
int MPI_Get_processor_name(
char* name, //返回处理器名称
int* resultlen //返回名字所占字节
)
MPI_Get_version
//获取MPI版本号
int MPI_Get_version(
int* version, //返回版本号前面的,如版本号2.0,这儿返回2
int* subversion //返回版本号后面的,如版本号2.0,这儿返回0
)
MPI_Wtime和MPI_Wtick
double MPI_Wtime(void) //返回墙上时间,用浮点数表示秒
double MPI_Wtick(void) //返回时钟精度
MPI_Get_count
//返回count参数接收到的元素数量
int MPI_Get_count(
MPI_Status* status_p, //接受消息时返回的状态
MPI_Datatype type, //MPI数据类型
int* count_p //返回值,返回元素数量
)
MPI_Reduce
全局求和函数,分担0号进程的压力,比如说,让3号进程接收一部分消息,然后再发送给0号进程。而实现这种任务的最优分配就是MPI_Reduce的工作。
MPI_Send与MPI_Recv是点对点通信,而MPI_Reduce实现了集合通信。
int MPI_Reduce(
void* input_data_p,
void* outut_data_p,
int count,
MPI_Datatype datatype, //MPI数据类型
MPI_Op operator //MPI操作数类型
int dest_process,
MPI_Comm comm //通信子,如MPI_COMM_WORLD
)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NBy8S8jI-1606720724613)(C:\Users\zzj\AppData\Roaming\Typora\typora-user-images\image-20201123161407294.png)]
MPI_Barrier
//用于进程间的同步,即一个进程调用该函数后需要等待通信器内所有进程调用该函数后返回
int MPI_Barrier(MPI_Comm comm)
MPI_Bcast
//广播
//root进程将自己buffer内的数据发给通信器内所有进程
//非root进程用自己的buffer接收数据
int MPI_Bcast(
void* buffer, //起始地址
int count,
MPI_Datatype datatype,
int root,
MPI_Comm comm
)
MPI_Gather
//收集
//所有进程(包括根进程)将sendbuf的数据传输给根进程
//根进程按着进程号顺序依次接收到recvbuf
int MPI_Gather(
void* sendbuf,
int sendcount,
MPI_Datatype sendtype,
void* recvbuf,
int recvcount,
MPI_Datatype recvtype,
int root,
MPI_Comm comm
)
MPI_Scatter
//散发:按数据在发送缓冲区的次序向各进程发送
int MPI_Scatter(
void* sendbuf,
int sendcount,
MPI_Datatype sendtype,
void* recvbuf,
int recvcount,
MPI_Datatype recvtype,
int root,
MPI_Comm comm
)
MPI_Op_create
//创建新运算
int MPI_Op_create(
MPI_User_function* func,
int commute,
MPI_Op* op
)
func
//用户自定义函数
void func(
void* invec,
void* inoutvec,
int* len,
MPI_Datatype* datatype
)
概念部分
阻塞式通信
- 是否对发送数据进行缓存,由MPI系统决定,而非程序员。
- 阻塞:
- 发送成功
- 消息成功发送
- 消息被缓存
- 接受成功
- 消息被成功接收
通信模式 | 发送 | 接收 |
标准通信模式 | 发送 | 接收 |
标准通信模式 | MPI_SEND | MPI_RECV |
缓存通信模式 | MPI_BSEND | |
同步通信模式 | MPI_SSEND | |
就绪通信模式 | MPI_RSEND |
标准模式(standard mode)
- 由MPI系统来决定是否将消息拷贝至一个缓冲区然后立即返回(此时消息的发送由MPI系统在后台进行),还是等待将数据发送出去后再返回。大部分MPI系统预留了一定大小的缓冲区,当发送的消息长度小于缓冲区大小时会将消息缓冲然后立即返回,否则,当部分或全部消息发送完成后才返回。
- 标准模式发送被称为是非局部的,因为它的完成可能需要与接收方联络。
- 标准模式的阻塞发送函数为MPI_Send。
缓冲模式(buffered mode)
- MPI系统将消息拷贝至一个用户提供的缓冲区然后立即返回,消息的发送由MPI系统在后台进行。用户必须确保所提供的缓冲区大小足以容下采用缓冲模式发送的消息。
- 缓冲模式发送操作被称为是局部的,因为它不需要与接收方联络即可立即完成(返回)。
- 缓冲模式的阻塞发送函数为 MPI_Bsend。
同步模式(synchronous mode)
- 实际上就是在标准模式的基础上还要求确认接收方已经开始接收数据后函数调用才返回。
- 显然,同步模式的发送是非局部的。
- 同步模式的阻塞发送函数为MPI_Ssend。
就绪模式(ready mode)
- 调用就绪模式发送时必须确保接收方已经处于就绪状态(正在等待接收该消息),否则该调用将产生一个错误。该模式设立的目的是在一些以同步方式工作的并行系统上,由于发送时可以假设接收方已经在接收而减少一些消息发送的开销。如果一个使用就绪模式的MPI程序是正确的,则将其中所有就绪模式的消息发送改为标准模式后也应该是正确的。
- 就绪模式的阻塞发送函数为 MPI_ Rsend。
非阻塞式通信
通信类型 | 函数返回 | 对数据操作 | 特性 |
阻塞式通信 | 1. 阻塞型函数需要等待指定操作完成返回 2. 或所涉及操作的数据要被MPI系统缓存安全备份后返回 | 函数返回后,对数据区操作是安全的 | 1. 程序设计相对简单 2. 使用不当容易造成死锁 |
非阻塞式通信 | 1. 调用后立刻返回,实际操作在MPI后台执行 2. 需调用函数等待或查询操作的完成情况 | 函数返回后,即操作数据区不安全,可能与后台正进行的操作冲突 | 1. 可以实现计算与通信的重叠 2. 程序设计相对复杂 |
一些概念:
- 同步:函数没有执行完不返回,线程被挂起;
- 阻塞:没有收完数据函数不返回,线程也被挂起;
- 异步:函数立即返回,通过事件或是信号通知调用者;
- 非阻塞:函数立即返回,通过select通知调用者
数据类型
MPI数据类型 | 对应的C数据类型 |
MPI_INT | int |
MPI_FLOAT | float |
MPI_DOUBLE | double |
MPI_SHORT | short |
MPI_LONG | long |
MPI_CHAR | char |
MPI_UNSIGNED_CHAR | unsigned char |
MPI_UNSIGNED_SHORT | unsigned short |
MPI_UNSIGNED | unsigned |
MPI_UNSIGNED_LONG | unsigned long |
MPI_LONG_DOUBLE | long double |
MPI_BYTE | |
MPI_PACKED |
数据类型:
- 类型序列:一组数据类型
- Typesig = {type0 , type1 , … , typen-1}
- 位移序列:一组整数位移
- Typedisp = {disp0 , disp1 , … , dispn-1}
- 类型图:类型序列和位移序列的元素一一配对构成的序列
- Typemap = {(type0 , disp0) , (type1 , disp1) , … , (typen-1 , dispn-1)}
- 假设数据缓冲区起始地址为buff,则上面这个类型图定义的数据类型包含n块数据,第i块数据地址为buff + dispi ,类型为typei 。
- 下界:数据的最小位移
- 上界:数据的最大位移+1
- 域:上界与下界的差
聚合通信
定义:多个进程之间的通信
三种
- 一对多
- 多对一
- 多对多
同步
- MPI_Barrier
广播
- MPI_Bcast
收集
- MPI_Gather
- 数据连续存放
- MPI_Gatherv
- 数据不一定连续存放
- MPI_Allgather
- 全收集
- 相当于未根进程调用n_procs次普通收集函数MPI_Gather
散发
- MPI_Scatter
- 连续
- MPI_Scatterv
- 不一定连续
- MPI_Alltoall
- 全散发收集
归约
- MPI_Reduce
- 各进程提供数据(sendbuf, count, datatype)
- 归约结果存放在root进程的缓冲区recvbuf
- MPI_Allreduce
- 全归约
- MPI_Reduce_scatter
- 归约散发