我在研究mpi send和recv函数的例子时有一个疑问

I have a question while studying the example of mpi send and recv function

下面的代码在学习发送和响应函数的同时展示了一个pingpong的例子。 但是我不明白 parter_rank .

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

int main(int argc, char** argv) {
const int PING_PONG_LIMIT = 10;

// Initialize the MPI environment
MPI_Init(NULL, NULL);
// Find out rank, size
int world_rank;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
int world_size;
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
 
// We are assuming 2 processes for this task
if (world_size != 2) {
fprintf(stderr, "World size must be two 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);}
  }
  MPI_Finalize();
}

Q1。很明显,上面的MPI_Comm_rank定义为world_rank,但是我不明白下面的partner_rank是什么意思。 这两个排名有什么区别?

Q2.When没看懂 if (world_rank == ping_pong_count % 2) 我们不能只写“rank ==0”和“rank ==1”吗?你为什么把算术运算符放在那里?

非常感谢您的评论。

这种看似不必要的算法和引入额外秩变量的原因是代码对称性。也就是说,由于最常见的 MPI 程序类型是作为 同一程序 多个副本 运行的程序,对称代码意味着代码不没有条件语句将排名与特定常量进行比较。为什么这很重要?因为它让代码更灵活,更容易理解。

比较 two-rank MPI ping-pong 程序的以下两个等效规范:

规格 1

  • 排名 0 向排名 1 发送消息
  • 排名 1 然后将消息发送回排名 0
  • 过程重复N次

本规范在 C-like 伪代码中的实现可以是:

loop N times {
   if (rank == 0) {
      MPI_Send to 1
      MPI_Recv from 1
   }
   else if (rank == 1) {
      MPI_Recv from 0
      MPI_Send to 0
   }
}

规格 2

  • 每个级别从另一个级别接收
  • 每个等级然后发送到另一个等级
  • 过程重复N-1次
  • 另外,rank 0 通过发送给其他 rank 来启动进程,并通过从其他 rank 接收最后一条消息来结束进程

可能的伪代码实现:

other_rank = 1 - rank

if (rank == 0) {
   MPI_Send to other_rank
}

loop N-1 times {
   MPI_Recv from other_rank
   MPI_Send to other_rank
}

if (rank == 0) {
   MPI_Recv from other_rank
}

第二个规范(及其实现)乍一看可能更复杂,但事实并非如此。它的优势在于它是本地的——它不会给出特定等级必须做什么的全球规定。相反,它给出了系统中任何等级的作用的处方,仅在过程开始和结束时破坏对称性,因为必须启动链。

如果我们想要扩展系统并且没有两个,而是三个等级在一个环中传递消息怎么办?我们希望等级 0 将消息传递给等级 1,然后等级 1 将其传递给等级 2,等级 2 又将其传递回等级 0。扩展规范 1 导致:

  • 排名 0 发送到排名 1
  • 等级 0 从等级 2 接收
  • 等级 1 从等级 0 接收
  • 排名 1 发送到排名 2
  • 等级 2 从等级 1 接球
  • 排名 2 发送到排名 0

在伪代码中:

loop N times {
   if (rank == 0) {
      MPI_Send to 1
      MPI_Recv from 2
   }
   else if (rank == 1) {
      MPI_Recv from 0
      MPI_Send to 2
   }
   else if (rank == 2) {
      MPI_Recv from 1
      MPI_Send to 0
   }
}

尝试将其扩展到四级,然后再扩展到五级。

另一方面,规范 2 自然扩展到三、四...实际上,扩展到任意数量的等级:

  • 每个级别从上一个级别接收
  • 每个级别发送到下一个级别
  • 等级 0 通过发送第一条消息启动进程,并通过接收最后一条消息结束进程

在伪代码中:

prev_rank = (rank - 1 + #ranks) % #ranks
next_rank = (rank + 1) % #ranks

if (rank == 0) {
   MPI_Send to next_rank
}

loop N-1 times {
   MPI_Recv from prev_rank
   MPI_Send to next_rank
}

if (rank == 0) {
   MPI_Recv from prev_rank
}

值得注意的是,规范 2 只不过是此通用规范的一个特定情况,#ranks 等于 2。在这种情况下,prev_ranknext_rank 都等于 (rank + 1) % 2,即是一个相同的等级。此外,当排名取 01.

的值时,(rank + 1) % 21 - rank 相同

我希望您现在明白背后的动机不是将特定操作硬编码到特定级别,而是使用本地算术来确定要做什么。在您的情况下,每个偶数 ping 消息值都按等级 0 递增,每个奇数 ping 消息值按等级 1 递增,但是如果您将它扩展到一个等级环怎么办? if (rank == ping_value % #ranks) ping_value++; 有点做正确的事并且适用于任何数量的等级。