基本函数
6个基本函数
MPI 有6个基本函数,从理论上讲 MPI 的所有通信功能都可以使用它的6个基本函数来实现。这 6 个函数为
// 初始化
MPI_Init(int *argc, char ***argv)`
// MPI 结束调用
MPI_Finalize(void)
/**
* 获得进程的标识号
* 进程标保存到 rank 里
*/
MPI_Comm_rank(MPI_Comm comm, int *rank)
/**
* 获得当前通信域中进程的个数
* 进程数量保存到 size 里
*/
MPI_Comm_size(MPI_Comm comm, int *size)`
// 发送消息
MPI_Send(void * buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
// 接受消息
MPI_Recv(void * buf, int count, MPI_Datatype data, int source, int tag, MPI_Comm comm, MPI_Status *status)
信息传递
MPI 通过 MPI_Send
和 MPI_Receive
发送和接受消息。其中这两种属于阻塞通信,对于发送方来说,消息发送不出去就会一直等待,而对于接收方来说,接收不到消息就会一直等待。MPI_Send
各参数的含义:
buf
- 发送缓冲区的起始位置count
- 发送的数据个数datatype
- 发送数据的数据类型dest
- 目标进程标号tag
- 消息标志comm
- 通信域
MPI_Recv
各参数含义:
buf
- 接受缓冲区地址count
- 最多可接受的数据个数datatype
- 接受的数据类型source
- 发送数据的进程号tag
- 消息标志comm
- 通信域status
- 返回状态
发送和接受的时候是以指定的 datatype 为基本单位的,count 是 datatype 类型数据的数目。在接受数据时,接受缓冲区的长度可以大于发送数据的长度。但是 MPI 中没有数据截断,如果发送数据长度大于接受缓冲区的长度就会报错。
在 C 实现中,状态变量 MPI_Status
必须要包含 3 个信息:MPI_SOURCE
, MPI_TAG
和 MPI_ERROR
,除此之外还可以包含其它的附加域。
通过 MPI_Status
,我们可以获得下面的三种主要信息
- 发送进程的标号,存放在
MPI_SOURCE
属性中 - 消息的标记号,存放在
MPI_TAG
属性中 - 消息的长度,通过借助于
MPI_Get_count
函数,将 status 变量和数据类型传入,消息长度存放在count
中MPI_Get_count( MPI_Status* status, MPI_Datatype datatype, int* count)
预定义数据类型
MPI 预定义了下面几种数据类型
MPI预定义数据类型 | 对应的C数据类型 |
---|---|
MPI_CHAR | signed char |
MPI_SHORT | signed short int |
MPI_INT | signed int |
MPI_LONG | signed long int |
MPI_LONG_LONG_INT | long long int (optional) |
MPI_UNSIGNED_CHAR | unsigned char |
MPI_UNSIGNED_SHORT | unsigned short int |
MPI_UNSIGNED | unsigned int |
MPI_UNSIGNED_LONG | unsigned long int |
MPI_FLOAT | float |
MPI_DOUBLE | double |
MPI_LONG_DOUBLR | long double |
MPI_BYTE | 无 |
MPI_PACKED | 无 |
在传递信息的时候,要保证两个方面的类型匹配(除了 MPI_BYTE
和 MPI_PACKED
):
- 传输的数据类型和通信中声明的 MPI 类型要对应,即数据类型为
int
, 那么通信时声明的数据类型就要为MPI_INT
。 - 发送方和接受方的类型要匹配
MPI_BYTE
和 MPI_PACKED
可以和任意以字节为单位的存储相匹配。 MPI_BYTE
可以用于不加修改的传送内存中的二进制值。
任意源和任意标识
在消息传递时,发送操作必须明确指定发送对象的进程标号和消息标识,但是接收消息时,可以通过使用 MPI_ANY_SOURCE
和 MPI_ANY_TAG
来接受任意进程发送给本进程的消息,类似于通配符。MPI_ANY_SOURCE
和 MPI_ANY_TAG
可以同时使用或者分别单独使用。
其他常用函数
这里记录一下 MPI 中其他的常用函数
获得当前时间
double MPI_Wtime(void)
获得机器名字
int MPI_Get_processor_name(char *name, int *result_len)
name
- 当前进程所在机器的名字result_len
- 返回名字的长度
获得 MPI 版本
int MPI_Get_version(int *version, int *subversion)
version
- MPI 主版本号subversion
- MPI 次版本号
判断 MPI_Init 是否执行,唯一一个可以在 MPI_Init 之前调用的函数。
int MPI_Initialized(int *flag)
-flag
- 是否已调用
使通信域 Comm 中的所有进程退出
int MPI_Abort(MPI_Comm comm, int errorcode)
comm
- 退出进程所在的通信域errorcode
- 错误码
示例
线程依次传参
#include <stdio.h>
#include <stdlib.h>
#include "mpi.h"
/**
* 数据传递
*
* 线程 i 向线程 i+1 传数据
*/
int main() {
int param;
int process_num;
int process_id;
MPI_Status status;
MPI_Init(NULL, NULL);
MPI_Comm_rank(MPI_COMM_WORLD, &process_id);
MPI_Comm_size(MPI_COMM_WORLD, &process_num);
if(process_id == 0) {
param = 3;
printf("rank 0 send rank 1: %d\n", param);
param++;
MPI_Send(¶m, 1, MPI_INT, 1, 99, MPI_COMM_WORLD);
} else {
MPI_Recv(¶m, 1, MPI_INT, process_id - 1, 99, MPI_COMM_WORLD, &status);
printf("rank %d receive from %d: %d\n",process_id, process_id-1, param);
if(process_id < process_num -1) {
param++;
printf("rank %d send rank %d: %d\n", process_id, process_id+1, param);
MPI_Send(¶m, 1, MPI_INT, process_id + 1, 99, MPI_COMM_WORLD);
}
}
MPI_Finalize();
}
运行结果
rank 0 send rank 1: 3
rank 1 receive from 0: 4
rank 1 send rank 2: 5
rank 2 receive from 1: 5
rank 2 send rank 3: 6
rank 3 receive from 2: 6
使用 MPI_BYTE
下面是一个使用 MPI_BYTE
传递自定义结构体的例子。
#include <stdio.h>
#include <stdlib.h>
#include "mpi.h"
typedef struct _custom_t {
int a;
double b;
} custom_t;
int main() {
int rank;
MPI_Status status;
MPI_Init(NULL, NULL);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
custom_t msg;
if(rank == 0) {
msg.a = 2;
msg.b = 10.0;
MPI_Send(&msg, sizeof(custom_t), MPI_BYTE, 1, 99, MPI_COMM_WORLD);
}
if(rank == 1) {
MPI_Recv(&msg, sizeof(custom_t), MPI_BYTE, 0, 99, MPI_COMM_WORLD, &status);
printf("msg: a is %d and b is %.2f\n", msg.a, msg.b);
}
MPI_Finalize();
}
获得接受信息的长度
在接收完消息后,获得消息的长度。要求接受缓冲区的长度要大于消息的长度
#include <stdio.h>
#include <stdlib.h>
#include "mpi.h"
int main() {
int n = 10;
int half_n = 5;
int buffer[n];
int i;
int rank;
MPI_Status status;
for(i = 0; i < half_n; i++) {
buffer[i] = i;
}
MPI_Init(NULL, NULL);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if(rank == 0) {
MPI_Send(&buffer,half_n, MPI_INT, 1, 99, MPI_COMM_WORLD);
}
if(rank == 1) {
int receive_count = 0;
MPI_Recv(&buffer, n, MPI_INT, 0, 99, MPI_COMM_WORLD, &status);
MPI_Get_count(&status, MPI_INT, &receive_count);
printf("rank 1 receive %d int\n", receive_count);
for(i = 0; i < n; i++) {
printf("buffer[%d] is %d\n", i, buffer[i]);
}
}
MPI_Finalize();
}
上面的方法不太灵活,因为在接受消息的时候我们仍然不知道消息的长度,在 Recv
函数中的 count
不太好指定。通过使用 MPI_Probe
方法,我们可以事先获取要接受的消息的相关信息,可以灵活的接受消息。
下面是 MPI_Probe
函数的原型:
MPI_Probe(
int source,
int tag,
MPI_Comm comm,
MPI_Status* status)
通过传入发送进程的进程号,消息标记以及通信域,就可以提前获得消息的 status,下面是一个示例:
#include <stdio.h>
#include <stdlib.h>
#include "mpi.h"
int main() {
int n = 10;
int half_n = 5;
int buffer[n];
int i;
int rank;
MPI_Status status;
for(i = 0; i < half_n; i++) {
buffer[i] = i;
}
MPI_Init(NULL, NULL);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if(rank == 0) {
MPI_Send(&buffer,half_n, MPI_INT, 1, 99, MPI_COMM_WORLD);
}
if(rank == 1) {
int receive_count = 0;
MPI_Probe(0, 99, MPI_COMM_WORLD, &status);
// 在实际接受消息之前获得消息的长度
MPI_Get_count(&status, MPI_INT, &receive_count);
MPI_Recv(&buffer, receive_count, MPI_INT, 0, 99, MPI_COMM_WORLD, &status);
printf("rank 1 receive %d int\n", receive_count);
for(i = 0; i < n; i++) {
printf("buffer[%d] is %d\n", i, buffer[i]);
}
}
MPI_Finalize();
}
使用任意源和任意标识
线程 1 到 n-1 向线程 0 发送消息
#include <stdio.h>
#include <stdlib.h>
#include "mpi.h"
int main() {
int rank, size;
int value = 0;
int i;
MPI_Status status;
MPI_Init(NULL, NULL);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
if(rank == 0) {
for(i = 0; i < size -1; i++) {
MPI_Recv(&value, 1, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
printf("receive from process %d and values is %d\n", status.MPI_SOURCE, value);
}
} else {
value = rank * 2;
MPI_Send(&value, 1, MPI_INT, 0, rank, MPI_COMM_WORLD);
}
MPI_Finalize();
}
示例输出
receive from process 2 and values is 4
receive from process 3 and values is 6
receive from process 1 and values is 2