MPI缓冲区
- 由MPI自行维护的一块内存区域,也可由用户(MPI_Bsend)自行维护;
- 发送方 维护一块发送缓冲区; 接收方 维护一块接收缓冲区。
数据收发过程:
- 当发送端将数据拷贝到自身的数据缓冲区后(注意这里是拷贝,即数据到达发送缓冲区,再对原数据进行修改将不会影响发送的数据),对应的Send函数将会返回,意味着发送动作已经完成;
- 当接收端将数据拷贝到自身的接收缓冲区中,Recv函数调用结束。
类似于Socket通信时,调用send将数据放置发送缓冲区即表示send完成。
阻塞通信模型
发送
1. 标准通信模式 MPI_Send 该接口是否进行缓存由MPI决定,分情况讨论
- 发送进程的发送动作不依赖接收进程(有缓存):发送进程将数据拷贝到数据缓冲区,不管接收进程有没有执行接收动作,函数都直接返回,发送动作对于用户来讲已经完成;
- 发送进程的发送动作依赖于接收进程(不带缓存,直接发送):发送进程发送消息时需要接收进程也要开始接收进程,两者处于一边发送一边接收的状态,此时MPI_Send阻塞当前发送方直到数据被接收方确认收到,基于底层的数据传输机制;
2. 缓存通信模式 MPI_Bsend 当用户对上述标准通信模式不满意,不能满足需求,可以采用该模式
该函数在发送消息时使用明确的缓冲区,并具有较低的内存使用率和较高的性能。相对于 MPI_Send 函数,MPI_Bsend 不阻塞发送方,也不会复制消息缓冲区中的数据,而是将数据拷贝到MPI缓冲区中,MPI_Bsend 函数将立即返回,在MPI缓冲区中的消息稍后使用异步方式传输。
函数原型
int MPI_Bsend(const void *buf, int count,
MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
//用户自行管理缓冲区
int MPI_Buffer_attach(void *buffer,int size)
int MPI_Buffer_detach(void *buffer,int &size)
参数详解
- buf: 待发送数据的首地址
- count: 待发送的数据量
- datatype: 待发送数据类型
- dest: 目标进程的 MPI rank
- tag: 消息标记
- comm: MPI通信域
注意事项
发送时需要将数据从消息缓冲区拷贝到用户提供的缓冲区buffer,该方法消除了发送端同步的开销,如前面分析,消息发送能否进行及能否正确返回不依赖于接收进程。好处是用户可以认为程序需要发送的消息提供缓冲区,但用户也需要负责管理该缓冲区。如果该缓冲区buffer大小不足以存储消息缓冲区中待发送的数据,将导致程序错误退出。
#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>#define BUFFER_SIZE 1024int main(int argc, char *argv[])
{int rank, size;MPI_Status status;double t0, t1;char buf[BUFFER_SIZE];char *my_buffer;MPI_Request request;MPI_Init(&argc, &argv);MPI_Comm_rank(MPI_COMM_WORLD, &rank);MPI_Comm_size(MPI_COMM_WORLD, &size);if(rank == 0){//为MPI缓冲区分配内存空间my_buffer=(char *)malloc(BUFFER_SIZE);MPI_Buffer_attach(my_buffer, BUFFER_SIZE);snprintf(buf, BUFFER_SIZE, "Hello, world, from rank %d!", rank);t0=MPI_Wtime();//异步发送消息MPI_Bsend(buf, BUFFER_SIZE, MPI_CHAR, 1, 0, MPI_COMM_WORLD, &request);MPI_Wait(&request, &status);t1=MPI_Wtime();printf("Time taken=%f\n", t1-t0);free(my_buffer);MPI_Buffer_detach(&my_buffer, &BUFFER_SIZE);}else{//为MPI缓冲区分配内存空间my_buffer=(char *)malloc(BUFFER_SIZE);MPI_Buffer_attach(my_buffer, BUFFER_SIZE);t0=MPI_Wtime();//等待接收消息MPI_Recv(buf, BUFFER_SIZE, MPI_CHAR, 0, 0, MPI_COMM_WORLD, &status);t1=MPI_Wtime();printf("Rank %d received message=%s, time=%f\n", rank, buf, t1-t0);free(my_buffer);MPI_Buffer_detach(&my_buffer, &BUFFER_SIZE);}MPI_Finalize();return 0;
}
3. 同步通信模式 MPI_Ssend
该模式的开始不依赖接收进程相应的接收操作是否已经启动,但是同步发送却必须等到相应的接收进程开始后才可以正确返回。即
- 发送方必须等待接收方的确认才能继续执行。
- 接收方必须收到消息后才能继续执行。
- 可以确保接收方正确接收消息。
函数原型
int MPI_Ssend(void* buf, int count, MPI_Datatype datatype,
int dest, int tag, MPI_Comm comm)
参数详解
- buf:指向待发送数据的指针。
- count:发送元素数量。
- datatype:发送元素的类型。
- dest:目标进程的进程号。
- tag:消息标签。
- comm:进程通信域
代码示例
#include <stdio.h>
#include <mpi.h>int main(int argc, char** argv) {int size, rank;int data;MPI_Status status;MPI_Init(&argc, &argv);MPI_Comm_size(MPI_COMM_WORLD, &size);MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if (rank == 0) {data = 12345;MPI_Ssend(&data, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);printf("Process 0 sent data %d to process 1\n", data);} else if (rank == 1) {MPI_Recv(&data, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);printf("Process 1 received data %d from process 0\n", data);}MPI_Finalize();return 0;
}
4. 就绪通信模式 MPI_Rsend
该模式只有当接收进程的接收操作已经启动时才可以在发送进程启动发送操作(即是一种发送并立即返回操作),否则,当发送操作启动而相应的操作还没有启动时,发送操作将出错,对于非阻塞发送操作的正确返回,并不意味着发送已经完成,但对于阻塞发送的正确返回,则代表发送缓冲区可以重复使用。
函数原型
int MPI_Rsend(const void *buf, int count, MPI_Datatype datatype,
int dest, int tag, MPI_Comm comm)
参数详解
- buf:指向待发送数据的指针。
- count:发送元素数量。
- datatype:发送元素的类型。
- dest:目标进程的进程号。
- tag:消息标签。
- comm:进程通信域
代码实例
#include <stdio.h>
#include <mpi.h>int main(int argc, char** argv) {int size, rank;int data;MPI_Status status;MPI_Init(&argc, &argv);MPI_Comm_size(MPI_COMM_WORLD, &size);MPI_Comm_rank(MPI_COMM_WORLD, &rank);if (rank == 0) {data = 12345;MPI_Request request;MPI_Rsend(&data, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);printf("Process 0 sent data %d to process 1\n", data);} else if (rank == 1) {MPI_Status status;int data_recv;MPI_Recv(&data_recv, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);printf("Process 1 received data %d from process 0\n", data_recv);}MPI_Finalize();return 0;
}
接收
- MPI_Recv
非阻塞通信模式
发送
标准通信模式 MPI_Isend
缓存通信模式 MPI_Ibsend
是 MPI_Bsend 的非阻塞版本,在 MPI_Bsend 的基础上,MPI_Ibsend 函数允许发送方在消息发送期间继续执行其他操作,而不必等待消息发送完成。MPI_Ibsend 函数将消息存储在缓冲区中,并返回一个 MPI_Request 对象,代表着消息的发送请求。
函数原型
int MPI_Ibsend(const void* buf, int count,
MPI_Datatype datatype, int dest, int tag,
MPI_Comm comm, MPI_Request* request)
参数详解
- buf:指向待发送数据的指针。
- count:发送元素数量。
- datatype:发送元素的类型。
- dest:目标进程的进程号。
- tag:消息标签。
- comm:进程通信域。
- request:MPI_Request 类型的指针,存储发送请求对象。
代码实例
#include <stdio.h>
#include <mpi.h>#define BUFFER_SIZE 1024int main(int argc, char** argv) {int size, rank;int data;MPI_Status status;char buffer[BUFFER_SIZE];MPI_Request request;MPI_Init(&argc, &argv);MPI_Comm_size(MPI_COMM_WORLD, &size);MPI_Comm_rank(MPI_COMM_WORLD, &rank);if (rank == 0) {data = 12345;MPI_Buffer_attach(buffer, BUFFER_SIZE);MPI_Ibsend(&data, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &request);printf("Process 0 sent data %d to process 1\n", data);MPI_Wait(&request, &status);MPI_Buffer_detach(buffer, &BUFFER_SIZE);} else if (rank == 1) {int data_recv;MPI_Recv(&data_recv, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);printf("Process 1 received data %d from process 0\n", data_recv);}MPI_Finalize();return 0;
}
同步通信模式 MPI_Issend
MPI_Ssend 的非阻塞版本,在 MPI_Ssend 的基础上,MPI_Issend 函数允许发送方在消息发送期间继续执行其他操作,而不必等待消息发送完成。MPI_Issend 函数将消息存储在缓冲区中,并返回一个 MPI_Request 对象,代表着消息的发送请求。
函数原型
int MPI_Issend(const void* buf, int count,
MPI_Datatype datatype, int dest, int tag,
MPI_Comm comm, MPI_Request* request)
参数详解
- buf:指向待发送数据的指针。
- count:发送元素数量。
- datatype:发送元素的类型。
- dest:目标进程的进程号。
- tag:消息标签。
- comm:进程通信域。
- request:MPI_Request 类型的指针,存储发送请求对象。
代码实例
#include <stdio.h>
#include <mpi.h>#define BUFFER_SIZE 1024int main(int argc, char** argv) {int size, rank;int data;MPI_Status status;char buffer[BUFFER_SIZE];MPI_Request request;MPI_Init(&argc, &argv);MPI_Comm_size(MPI_COMM_WORLD, &size);MPI_Comm_rank(MPI_COMM_WORLD, &rank);if (rank == 0) {data = 12345;MPI_Buffer_attach(buffer, BUFFER_SIZE);MPI_Issend(&data, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &request);printf("Process 0 sent data %d to process 1\n", data);MPI_Wait(&request, &status);MPI_Buffer_detach(buffer, &BUFFER_SIZE);} else if (rank == 1) {int data_recv;MPI_Recv(&data_recv, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);printf("Process 1 received data %d from process 0\n", data_recv);}MPI_Finalize();return 0;
}
就绪通信模式 MPI_Irsend
是 MPI_Rsend 的非阻塞版本,在 MPI_Rsend 的基础上,MPI_Irsend 函数允许发送方在消息发送期间继续执行其他操作,而不必等待消息发送完成。MPI_Irsend 函数将消息存储在缓冲区中,并返回一个 MPI_Request 对象,代表着消息的发送请求。
函数原型
int MPI_Irsend(const void* buf, int count,
MPI_Datatype datatype, int dest, int tag,
MPI_Comm comm, MPI_Request* request)
参数详解
- buf:指向待发送数据的指针。
- count:发送元素数量。
- datatype:发送元素的类型。
- dest:目标进程的进程号。
- tag:消息标签。
- comm:进程通信域。
- request:MPI_Request 类型的指针,存储发送请求对象。
代码实例
#include <stdio.h>
#include <mpi.h>#define BUFFER_SIZE 1024int main(int argc, char** argv) {int size, rank;int data;MPI_Status status;char buffer[BUFFER_SIZE];MPI_Request request;MPI_Init(&argc, &argv);MPI_Comm_size(MPI_COMM_WORLD, &size);MPI_Comm_rank(MPI_COMM_WORLD, &rank);if (rank == 0) {data = 12345;MPI_Buffer_attach(buffer, BUFFER_SIZE);MPI_Irsend(&data, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &request);printf("Process 0 sent data %d to process 1\n", data);MPI_Wait(&request, &status);MPI_Buffer_detach(buffer, &BUFFER_SIZE);} else if (rank == 1) {int data_recv;MPI_Recv(&data_recv, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);printf("Process 1 received data %d from process 0\n", data_recv);}MPI_Finalize();return 0;
}
注,MPI_Irsend 的缓冲区大小由 MPI_BSEND_BUFFER_SIZE 宏定义所决定。如果发送的消息大于缓冲区大小,将会导致 MPI_Irsend 函数阻塞
接收
- MPI_Irecv
区别:
- 是否要对发送的数据进行缓存;(缓存)
- 是否只有当接收调用执行后才可以执行发送操作;(同步)
- 什么时候发送调用可以正确返回;
- 发送调用正确返回是否意味着发送已完成。
几种特殊使用场景
短时间内有大量的短消息的发送
可能会导致缓冲区爆掉,导致消息丢失,用户可能需要使用MPI_Bsend控制缓冲区的使用;
如果短消息可以打包成一个大消息,降低发送次数
发送的数据量特别大
MPI提供的缓冲区的大小不够,可能导致数据丢失,从而导致程序卡,MPI_Bsend模式来直接控制缓冲区的使用;
可以进行分段发送,对数据进程拆分。