基础知识
编译与执行
编译和运行程序的细节主要取决于系统,很多系统都成为mpicc的命令来编译程序
mpicc -g -Wall -o mpi_hello mpi_hello.c
mpicc是C语言编译器的包装脚本(wrapper script)。包装脚本的主要目的是运行某个程序。指定使用c99
mpiexec -n <number of processes> ./mpi_hello
很多系统还支持用mpiexec命令来启动程序
例如:使用1个进程运行程序
mpiexec -n 1 ./mpi_hello
使用4个进程运行程序
mpiexec -n 4 ./mpi_hello
对于程序
#include <stdio.h>
#include <string.h>
#include <mpi.h>
const int MAX_STRING = 100;
int main(){
char greeting[MAX_STRING];
int comm_sz;
int my_rank;
MPI_Init(NULL, NULL);
MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
if(my_rank != 0) {
sprintf(greeting, "Greeting from process %d of %d!",my_rank,comm_sz);
MPI_Send(greeting, strlen(greeting)+1, MPI_CHAR, 0, 0, MPI_COMM_WORLD);
}
else {
printf("Greeting from process %d of %d!\n",my_rank, comm_sz);
for(int q = 1; q< comm_sz;q++){
MPI_Recv(greeting, MAX_STRING, MPI_CHAR, q,0,MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("%s\n",greeting);
}
}
MPI_Finalize();
return 0;
}
由于里面有C语言的新特性,可以指定
mpicc -g -Wall -std=c11 -o hello hello.c
运行4个进程,得到的输出
➜ mpiexec -n 4 ./hello
Greeting from process 0 of 4!
Greeting from process 1 of 4!
Greeting from process 2 of 4!
Greeting from process 3 of 4!
MPI程序
所有的MPI定义的标识符都是由字符串MPI_
开始,下划线后的第一个字符大写,表示函数名和MPI定义的类型。MPI定义的宏和常量的所有字母都是大写的。
MPI_Init和MPI_Finalize
调用MPI_Init是为了告知系统进行所有必要的初始化设置。
int MPI_Init(
int * argc_p /*in/out*/,
char ** argv_p /*in/out*/
);
参数argc_p
个argv_p
是指向参数argc
和argv
的指针。当不需要的时候设置为NULL。
调用MPI_Finalize
是为了告知MPI系统MPI已经使用完毕。为MPI分配的任何资源都可以释放了。
MPI程序的基础构架
#include <mpi.h>
int main(int argc, char * argv[]) {
MPI_Init(&argc, &argv);
...
MPI_Finalize();
return 0;
}
通信子
通信子(communicator)指的是一组可以互相发送信息的进程集合。MPI_Init的其中一个目的,是在用户启动程序时,定义有用户启动的所有进程组成的通信子。称为MPI_COMM__WORLD
。
int MPI_Comm_size(
MPI_Comm comm ,
int * comm_sz_p
);
int MPI_Comm_rank(
MPI_Comm comm,
int * my_rank_p
);
第一个参数是一个通信子,它所属的类型是MPI的通信子定义的特殊类型:MPI_Comm
MPI_Comm_size 函数在它的第二个参数返回通信子的进程数。MPI_Comm_rank函数在它的第二个参数返回正在调用进程的通信子中的进程号。
MPI_Send
有1,2,⋯.comm_sz−1号进程执行的发送其实很复杂的。
int MPI_Send(
void * msg_buf_p,
int msg_size,
MPI_Datatype msg_type,
int dest,
int tag,
MPI_Comm commmunicator
);
第一个参数msg_buf_p是一个指向消息内容的内存块的指针。
第二个参数和第三个参数:msg_size和msg_type 指定了要发送的数据量
MPI数据类型 | C语言数据类型 |
---|---|
MPI_CHAR | signed char |
MPI_SHORT | signed short int |
MPI_INT | signed int |
MPI_LONG | signed long int |
MPI_LONG_LONG | signed long long int |
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_DOUBLE | long double |
MPI_BYTE | |
MPI_PACKED |
第四个参数:dest指定了要接收消息的进程的进程号
第五个参数:tag是个非负int型,用于区分看上去完全一样的消息。
最后一个参数:是一个通信子,用于指定通信范围。通信子指的是一组互相发送消息的进程的集合。一个通信子的进程所发送的消息不能被另一个通信子的进程所接收。
MPI_Recv
int MPI_Recv(
void * msg_buf_p,
int buf_size,
MPI_Datatype buf_type,
int source,
int tag,
MPI_Comm communicator,
MPI_Status* status_p
);
参数source用来指定了接收消息应该从哪个进程发送来的,参数tag要与发送消息的参数tag相匹配。参数communicator必须与发送进程所用的通信子匹配。
消息匹配
假定q号进程调用MPI_Send()函数
MPI_Send(send_buf_p, send_buf_sz, send_type, dest, send_tag, send_comm)
假定r号进程调用了MPI_Recv()函数
MPI_Recv(recv_buf_p, recv_buf_sz, recv_type, src, recv_tag, recv_comm, &status)
则q号进程调用MPI_Send函数所发送的消息可以被r号进程调用MPI_Recv函数接收,如果
- recv_comm = send_comm
- recv_tag = send_tag
- dest=r & src =q
在多数的情况下,满足下面的规则就可以了
- 如果recv_type = send_type 同时recv_buf_sz ≥ send_buf_sz 那么由q号进程发送的消息就可以被r号进程成功的接收。
一个进程可以接收多个进程发送的消息,接收进程并不知道其他进程执行发送消息的顺序。MPI提供了一个特殊的常量MPI_ANY_SOURCE
,就可以传递给MPI_Recv。
for(i=1; i<comm_sz;i++) {
MPI_Recv(result, result_sz, result_type, MPI_ANY_SOURCE, result_tag, comm, MPI_STATUS_IGNORE);
Process_result(result);
}
类似的,一个进程有可能接收多条来自另一个进程的有着不同的标签的消息,并且接收进程不知道消息发送的顺序。
使用通配符(wildcard)参数时,需要注意的几点:
- 只要接收者可以调用通配符参数,发送者必须指定一个进程号与另一个非负整数标签。此外,MPI使用的是所谓的推(push)通信机制,而不是拉(pull)通信机制。
- 通信子参数没有通配符,发送者和接收者必须指定通信子。
status_p
MPI类型MPI_Status是一个有至少三个成员的结构,MPI_SOURCE,MPI_TAG和MPI_ERROR。假定程序有如下的定义:
MPI_Status status;
将&status
作为最后一个参数传递给MPI_Recv函数并调用它后,可以通过检查以下两个成员来确定发送者和标签。
status.MPI_SOURCE
status.MPI_TAG
接收量不是存储在应用程序可以直接访问到的域内,但是用户可以调用MPI_Get_count函数找到这个值。
int MPI_Get_count(
MPI_Status * status_p,
MPI_Datatype type,
int * count_p
);
MPI_Send和MPI_Recv
发送进程可以缓冲消息,也可以阻塞。
如果是缓冲消息,则MPI系统将会把消息放置在自己内部存储器里,并返回MPI_Send的调用。
如果是系统发生阻塞,那么它将一直等待,知道可以开始发送消息,并不立即返回MPI_Send的调用。
MPI_Send 的精确行为可以由MPI实现所决定的,但是,典型的实现方法有一个默认的消息截止大小,如果一条消息的大小小于截止大小,,就会被缓冲,如果大于截止大小,就会被阻塞。
MPI_Recv函数总是阻塞是,直到接收到一条匹配消息,当MPI_Recv函数调用返回时,就知道一条消息已经存储在接收缓冲区中,接收消息函数同样可以替代,系统检查是否有一条匹配的消息并返回。
MPI要求消息是不可超越的(nonvertaking),如果q号进程发送两条消息给r号进程,那么q号发送的第一条消息必须在第二条消息之前可用,但是如果消息来自不同的进程的消息的到达顺序是没有限制的。