MPI Tutorial 笔记(四) 动态消息传输(点对点)

我们之前使用 MPI_SendMPI_Recv 执行标准的Blocking Point-to-Point Communication(阻塞点对点通信)

但是 我们那时候发送的是接收者事先已知长度的消息(即约定消息长度的传输)

>现在我们学习如何Blocking Point-to-Point的进行不约定消息长度(动态消息)的传输== (接收者事先不知道长度的消息)

我们先介绍MPI_Recv() 的最后一个参数MPI_Status* status,我们在上一节用MPI_STATUS_IGNORE忽略它了.

MPI_Status 结构体中的主要信息

他的地址是MPI_Recv()函数的最后一个参数,如果我们将 MPI_Status结构体地址传递给 MPI_Recv() 函数,则操作完成后将在该结构体中填充有关接收操作的信息 :

主要信息有:

  1. 发送端秩(MPI_SOURCE) :
    发送端的秩存储在结构体的 MPI_SOURCE 元素中。也就是说,如果我们声明一个 MPI_Status stat 变量,则可以通过 stat.MPI_SOURCE 访问秩

  2. 消息的标签(MPI_TAG):
    消息的标签可以通过结构体的 MPI_TAG 元素访问(类似于 MPI_SOURCE)(即 stat.MPI_TAG)。

  3. 消息的长度 😗*MPI_Status 中有它的信息,但是没有预定义的元素,故我们要使用另一个函数 MPI_Get_count 把消息从MPI_Status结构体中解析出来 **

MPI_Get_count

MPI_Get_count(
	MPI_Status* status,
	MPI_Datatype datatype,
	int* count
)

第一个参数:MPI_Status* status 传递的是MPI_status结构体的指针
第二个参数:MPI_Datatype datatype传递的是消息的数据类型
第三个参数:int* count是传递解析完MPI_Status* status中的关于消息长度的信息存放的地址
(即它指向的地方就存着 消息的长度)

提出一个问题:

我们MPI_Recv() 的参数中都包含了一些信息了,为什么我们还需要 MPI_Status 存下这三个信息?

  1. 事实证明,MPI_Recv 可以将 MPI_ANY_SOURCE 用作发送端的秩,将 MPI_ANY_TAG 用作消息的标签。 在这种情况下,MPI_Status 结构体是找出消息的实际发送端和标签的唯一方法。
  2. 此外,并不能保证 MPI_Recv 能够接收函数调用参数的全部元素。 相反,它只接收已发送给它的元素数量(如果发送的元素多于所需的接收数量,则返回错误。) MPI_Get_count 函数用于确定实际的接收量。

应用: MPI_Status 结构体查询的示例

(还没到不约定消息长度(动态消息)的传输的时候)
> 这个还是约定长度的,因为接收端缓存和**接受数目(消息长度)**都是按可能出现的最大次数给的==

!!!重点 :
不约定消息长度(动态消息)的传输,则应该接收端缓存接受数目(消息长度) 都应该 动态分配(依消息大小分配)

程序将随机数量的数字发送给接收端,然后接收端找出发送了多少个数字:

#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc,char**argv){
MPI_Init(NULL, NULL);

  int world_size;
  MPI_Comm_size(MPI_COMM_WORLD, &world_size);
  if (world_size != 2) {
    fprintf(stderr, "Must use two processes for this example\n");
    MPI_Abort(MPI_COMM_WORLD, 1);
  }
  int world_rank;
  MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);

const int MAX_NUMBER = 100
int numbers[MAX_NUMBERS];
int number_amount;//我们用它来存:
//进程2(发送进程): 随机发送数字的个数
//进程1(接收进程): MPI_Status 解析出来的消息长度

if (world_rank == 0){//进程 0
//Pick a random amount of integers to send to process 1
	srand(time(NULL));//初始化随机数构造器
	number_amount = (rand() / (float)RAND_MAX) * MAX_NUMBERS;//通过这个方法将随机数量的数字发送给接收端

	MPI_Send(numbers,number_amount,MPI_INT
			,1,0,MPI_COMM_WORL);
	printf("0 sent %d numbers to 1\n",number_amount);
}else if(world_rank == 1){
	MPI_Status status;
	// Receive at most MAX_NUMBERS from process 0
	MPI_Recv(numbers, MAX_NUMBERS, MPI_INT, 0, 
			0,MPI_COMM_WORLD,&status)			
	// After receiving the message, check the status to determine
    // how many numbers were actually received
    MPI_Get_count(&status, MPI_INT, &number_amount);
    
	// Print off the amount of numbers, and also print additional
    // information in the status object
    printf("1 received %d numbers from 0. Message source = %d, ""tag = %d\n",number_amount,status.MPI_SOURCE,status.MPI_TAG);
}
  MPI_Barrier(MPI_COMM_WORLD);
  MPI_Finalize();
}

==为什么不初始化numbers[MAX_NUMBERS]就发送? ==

因为不初始化它里面就是随机值 😃

如我们所见,进程 0 将最多 MAX_NUMBERS 个整数以随机数量发送到进程 1。
进程 1 然后调用 MPI_Recv 以获取总计 MAX_NUMBERS 个整数。
尽管进程 1 以 MAX_NUMBERS 作为 MPI_Recv 函数参数,但进程 1 将最多接收到此数量的数字(即接收数目<=MAX_NUMBERS)。
在代码中,进程 1 使用 MPI_INT 作为数据类型的参数,调用 MPI_Get_count,以找出实际接收了多少个整数。
除了打印出接收到的消息的大小外,进程 1 还通过访问 status 结构体的 MPI_SOURCEMPI_TAG 元素来打印消息的来源和标签。

TIPS: MPI_Get_count 解析消息长度时,的数据类型要和接收到的类型一致:
MPI_Get_count 的返回值是相对于传递的数据类型而言的。 如果用户使用 MPI_CHAR 作为数据类型,则返回的数量将是原来的四倍(假设整数是四个字节,而 char 是一个字节)

编译和运行: (-np 时number processes 😃)))

mpicc check_status.c -o check_status

mpirun -np 2 ./check_status

输出的例子:

0 sent 92 numbers to 1
1 received 92 numbers from 0. Message source = 0, tag = 0

正如预期的那样,进程 0 将随机数目的整数发送给进程 1,进程 1 将打印出接收到的消息的有关信息

下文,我们开始讲进行不约定消息长度(动态消息)的传输 (接收者事先不知道长度的消息):

使用 MPI_Probe 找出消息大小

除了传递接收消息并简易地配备一个很大的缓冲区来为所有可能的大小的消息提供处理(就像我们在上一个示例中所做的那样)(上一个例子的不聪明的方法

现在有MPI_Probe()我们有聪明的方法了:
您可以使用 MPI_Probe 在实际接收消息之前查询消息大小

MPI_Probe(
    int source,
    int tag,
    MPI_Comm comm,
    MPI_Status* status)
第一个参数 int source:发送端的rank--->因为发送端叫Source
第二个参数 int tag : 数据标签
第三个参数:MPI_Comm comm: communicator 类型
最后一个参数 MPI_Status* status: 把打探到的信息放在 status 结构体里 :)))))))

MPI_Probe() 看起来与 MPI_Recv() 非常相似。

  1. 实际上,您可以将 MPI_Probe 视为 MPI_Recv除了不接收消息外,它们执行相同的功能
  2. MPI_Recv 类似,MPI_Probe阻塞具有匹配标签和发送端的消息。
  3. 当消息可用时,它将填充 status 结构体
    ----->由此我们可以通过这个MPI_Probe()函数来了解将要接收消息的信息(存在一个MPI_Status中)—>由此我们可以在接收时根据这个信息来知道将要接收的信息大小与生成合适大小的缓存空间---->实现 动态消息传输

😃))))))))))
然后,用户可以使用 MPI_Recv 接收实际的消息。

动态消息传递代码 :

和我们上一个例子类似,但我们用了更聪明的动态消息传递,而不是简易地配备一个很大的缓冲区来为所有可能的大小的消息提供处理 😦(((

重点区别是: 我们在接收线程1 中采用了MPI_Probe() 来在实际接收消息之前查询消息信息,从而得到消息大小,分配适当大小的缓冲区并接收数字(而不是简易地配备一个很大的缓冲区来为所有可能的大小的消息提供处理:))))))) )

...}
else if(world_rank == 1){
	MPI_Status status;
	//Probe for an incoming message from process 0
	MPI_Probe(0, 0, MPI_COMM_WORLD, &status);

	MPI_Get_count(&status, MPI_INIT, &number_amount);
	//MPI_Get_count 把从MPI_Probe得到的MPI_Staus结构体中的信息长度解析出来了

	//通过MPI_Get_count解析出的信息长度,来构型接收缓存
	int* number_buf=(int*)malloc(sizeof(int) * number_amount);

	MPI_Recv(number_buf, number_amount, MPI_INT,0,0,MPI_COMM_WORLD, MPI_STATUS_IGNORE);
	//MPI_STATUS_IGNORE: 这里就不用再接收一边MPI_Status了,前面我们已经用MPI_Probe() 接收一次啦
printf("1 dynamically received %d numbers from 0.\n",
           number_amount);
    free(number_buf);//别忘了free掉
}

与上一个示例类似,进程 0 选择随机数量的数字发送给进程 1。
不同之处在于,进程 1 现在调用 MPI_Probe,以找出进程 0 试图发送多少个元素(利用 MPI_Get_count)。 然后,进程 1 分配适当大小的缓冲区并接收数字。

完整程序:

#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(int argc, char** argv) {
  MPI_Init(NULL, NULL);

  int world_size;
  MPI_Comm_size(MPI_COMM_WORLD, &world_size);
  if (world_size != 2) {
    fprintf(stderr, "Must use two processes for this example\n");
    MPI_Abort(MPI_COMM_WORLD, 1);
  }
  int world_rank;
  MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);

  int number_amount;
  if (world_rank == 0) {
    const int MAX_NUMBERS = 100;
    int numbers[MAX_NUMBERS];
    
    // Pick a random amont of integers to send to process one
    
    srand(time(NULL));
    number_amount = (rand() / (float)RAND_MAX) * MAX_NUMBERS;
    
    // Send the amount of integers to process one
    
    MPI_Send(numbers, number_amount, MPI_INT, 1, 0, MPI_COMM_WORLD);
    printf("0 sent %d numbers to 1\n", number_amount);
  } else if (world_rank == 1) {
    MPI_Status status;
    
    // Probe for an incoming message from process zero
    
    MPI_Probe(0, 0, MPI_COMM_WORLD, &status);
    
    // When probe returns, the status object has the size and other
    // attributes of the incoming message. Get the size of the message.
    
    MPI_Get_count(&status, MPI_INT, &number_amount);
    
    // Allocate a buffer just big enough to hold the incoming numbers
    
    int* number_buf = (int*)malloc(sizeof(int) * number_amount);
    
    // Now receive the message with the allocated buffer
    
    MPI_Recv(number_buf, number_amount, MPI_INT, 0, 0, MPI_COMM_WORLD,
             MPI_STATUS_IGNORE);
    printf("1 dynamically received %d numbers from 0.\n",
           number_amount);
    free(number_buf);
  }
  MPI_Finalize();
}

尽管这个例子很简单,但是 MPI_Probe 构成了许多动态 MPI 应用程序的基础

e.g. 控制端/执行子程序在交换变量大小的消息时通常会大量使用 MPI_Probe

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值