[3.1] MPI的基础结构 & send / recv 机制

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 datatypeC equivalent
MPI_SHORTshort int
MPI_INTint
MPI_LONGlong int
MPI_LONG_LONGlong long int
MPI_UNSIGNED_CHARunsigned char
MPI_UNSIGNED_SHORTunsigned short int
MPI_UNSIGNEDunsigned int
MPI_UNSIGNED_LONGunsigned long int
MPI_UNSIGNED_LONG_LONGunsigned long long int
MPI_FLOATfloat
MPI_DOUBLEdouble
MPI_LONG_DOUBLElong double
MPI_BYTEchar

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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小马敲马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值