1. MPI基本概念
1.1 abstract
消息传递模型?它其实只是指程序通过在进程间传递消息(消息可以理解成带有一些信息和数据的一个数据结构)来完成某些任务。MPI在消息传递模型设计上的经典概念:
-
通讯器(communicator):一组能够互相发消息的进程。在这组进程中,每个进程会被分配一个序号,称作秩(rank),进程间显性地通过指定秩来进行通信。
-
点对点(point-to-point)通信:不同进程间发送和接收操作。一个进程可以通过指定另一个进程的秩以及一个独一无二的消息标签(tag)来发送消息给另一个进程。接受者可以发送一个接收特定标签标记的消息的请求(或者也可以完全不管标签,接收任何消息),然后依次处理接收到的数据。类似这样的涉及一个发送者以及一个接受者的通信被称作点对点(point-to-point)通信。
-
集体性(collective)通信:单个进程和所有其他进程通信,类似广播机制,MPI专门的接口来实现这个过程所有进程间的集体性通信。API:
操作 描述 结果返回给 MPI_Bcast
广播 所有进程 MPI_Gather
收集数据 根进程 MPI_Allgather
收集数据 所有进程 MPI_Scatter
分发数据 每个进程 MPI_Reduce
归约操作 根进程 MPI_Allreduce
归约操作 所有进程 MPI_Scan
前缀归约 每个进程 MPI_Gatherv
收集不同长度的数据 根进程 MPI_Scatterv
分发不同长度的数据 每个进程
2. 点对点通信
2.1 send & recv info
MPI的send和rece的方式:开始的时候,A 进程决定要发送消息给 B 进程。A进程就会把需要发送给B进程的所有数据打包好,放到一个缓存里面。所有data会被打包到一个大的信息里,因此缓存会被比作信封(就像我们把好多信纸打包到一个信封里面然后再寄去邮局)。数据打包进缓存后,通信设备(通常是网络)就需要负责把信息传递到正确的地方,这个正确的地方是根据rank确定的那个进程。
尽管数据已经被送达到 B 了,但是进程 B 依然需要确认它想要接收 A 的数据。一旦确认了这点,数据就被传输成功了。进程A回接受到数据传递成功的消息,然后才去干其他事情。
当A需要传递不同的消息给B,为了让B能够方便区分不同的消息,运行发送者和接受者额外地指定一些信息 ID (正式名称是标签, tags)。当 B 只要求接收某种特定标签的信息的时候,其他的不是这个标签的信息会先被缓存起来,等到 B 需要的时候才会给 B。
mpi_proto.h
中收发的定义:
MPI_Send(
const void *buf, // 数据缓存(要发送的数据存储的内存地址)
int count, // 数量(MPI_Send 会精确地发送 count 指定的数量个元素)
MPI_Datatype datatype, // 类型(发送的数据类型,例如 MPI_INT、MPI_FLOAT 等)
int destination, // 目标进程的 rank(表示要发送给哪个进程)
int tag, // 标签(用于标识消息的标签,发送和接收的标签需一致)
MPI_Comm communicator) // 通信器(指定通信域,例如 MPI_COMM_WORLD)
MPI_Recv(
void* data, // 数据缓存(接收到的数据存储的内存地址)
int count, // 数量(MPI_Recv 期望接收 count 指定数量的数据)
MPI_Datatype datatype, // 类型(接收的数据类型,例如 MPI_INT、MPI_FLOAT 等)
int source, // 源进程的 rank(表示从哪个进程接收数据,MPI_ANY_SOURCE 表示任意进程)
int tag, // 标签(用于标识消息的标签,MPI_ANY_TAG 表示任意标签)
MPI_Comm communicator, // 通信器(指定通信域,例如 MPI_COMM_WORLD)
MPI_Status* status) // 状态(用于存储接收操作的返回状态,包括来源、标签、数据长度等信息)
2.2 基础 MPI 数据结构
MPI 数据结构以及它们在 C 语言里对应的结构如下:
MPI datatype | C equivalent |
---|---|
MPI_SHORT | short int |
MPI_INT | int |
MPI_LONG | long int |
MPI_LONG_LONG | 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_UNSIGNED_LONG_LONG | unsigned long long int |
MPI_FLOAT | float |
MPI_DOUBLE | double |
MPI_LONG_DOUBLE | long double |
MPI_BYTE | char |
2.3 send和recv的两个范例:
exp1的代码:
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
int main(int argc, char** argv) {
MPI_Init(NULL, NULL);
int world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
if (world_size < 2) {
fprintf(stderr, "World size must be greater than 1 for %s\n", argv[0]);
MPI_Abort(MPI_COMM_WORLD, 1);
}
int number;
if (world_rank == 0) {
number = -1;
MPI_Send(&number, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
} else if (world_rank == 1){
MPI_Recv(&number, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("Process 1 received number %d from process 0\n", number);
}
MPI_Finalize();
return 0;
}
/******************************************************************
base) joker@joker-2 4.1 Send & Recv % mpirun -np 2 ./send_recv
Process 1 received number -1 from process 0
(base) joker@joker-2 4.1 Send & Recv % mpirun -np 1 ./send_recv
World size must be greater than 1 for ./send_recv
Abort(1) on node 0 (rank 0 in comm 0): application called MPI_Abort(MPI_COMM_WORLD, 1) - process 0
*******************************************************************/
代码当中的MPI_Comm_rank
用于获取当前进程在给定通信子(communicator)中的 rank(进程编号)。rank
是进程在 comm
(通信器)中的编号,编号从 0
开始。MPI_COMM_WORLD
是默认通信器,表示所有进程都属于这个通信器。MPI_Comm_size
是用于获取在给定通信器 comm
中的总进程数。MPI_Abort
是断言,强制终止通信器的所有进程,第二参数是非0值的话表示异常退出。stderr
是 C/C++ 中的标准错误流(standard error),在 fprintf
中,stderr
是立即输出的,不受缓冲区控制。MPI_STATUS_IGNORE
表示当前的MPI_Recv
不关心接收的状态信息。
代码逻辑:总线程数<2的情况直接abort
,然后正常情况下的话直接初始化一个number为-1从线程0发送到线程1。
exp2的代码:
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
int main(int argc, char** argv) {
const int PING_PONG_LIMIT = 10;
MPI_Init(NULL, NULL);
int world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
if (world_size != 2) {
fprintf(stderr, "World size must be 2 for %s\n", argv[0]);
MPI_Abort(MPI_COMM_WORLD, 1);
}
int ping_pong_count = 0;
int partner_rank = (world_rank + 1) % 2;
while (ping_pong_count < PING_PONG_LIMIT) {
if (world_rank == ping_pong_count % 2) {
// Increment the ping pong count before you send it
ping_pong_count++;
MPI_Send(&ping_pong_count, 1, MPI_INT, partner_rank, 0, MPI_COMM_WORLD);
printf("%d sent and incremented ping_pong_count %d to %d\n",
world_rank, ping_pong_count,
partner_rank);
} else {
MPI_Recv(&ping_pong_count, 1, MPI_INT, partner_rank, 0,
MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("%d received ping_pong_count %d from %d\n",
world_rank, ping_pong_count, partner_rank);
}
}
}
/******************************************************************
1 received ping_pong_count 1 from 0
0 sent and incremented ping_pong_count 1 to 1
1 sent and incremented ping_pong_count 2 to 0
0 received ping_pong_count 2 from 1
0 sent and incremented ping_pong_count 3 to 1
0 received ping_pong_count 4 from 1
0 sent and incremented ping_pong_count 5 to 1
0 received ping_pong_count 6 from 1
0 sent and incremented ping_pong_count 7 to 1
0 received ping_pong_count 8 from 1
0 sent and incremented ping_pong_count 9 to 1
0 received ping_pong_count 10 from 1
1 received ping_pong_count 3 from 0
1 sent and incremented ping_pong_count 4 to 0
1 received ping_pong_count 5 from 0
1 sent and incremented ping_pong_count 6 to 0
1 received ping_pong_count 7 from 0
1 sent and incremented ping_pong_count 8 to 0
1 received ping_pong_count 9 from 0
1 sent and incremented ping_pong_count 10 to 0
*******************************************************************/
进程0和进程1在轮流发送和接收 ping_pong_count。