В приведенном ниже коде показан пример игры в пинг-понг при изучении функции отправки и ответа. Но я не понял из 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. Когда я не понимаю if (world_rank == ping_pong_count % 2) Разве мы не можем просто написать «ранг == 0» и «ранг == 1»? Зачем вы ввели туда арифметический оператор?

Буду признателен за ваш комментарий.

0
songhae 14 Окт 2020 в 18:18

1 ответ

Лучший ответ

Причиной этой, казалось бы, ненужной арифметики и введения дополнительных переменных ранга является симметрия кода . То есть, поскольку наиболее распространенным типом программ MPI является тот, который выполняется как несколько копий одной и той же программы , симметричный код означает код, который не имеет условных операторов, сравнивающих ранжировать к определенным константам. Почему это важно? Потому что это делает код более гибким и понятным.

Сравните следующие две эквивалентные спецификации того, как выглядит двухранговая программа для настольного тенниса MPI:

Спецификация 1

  • ранг 0 отправляет сообщение на ранг 1
  • ранг 1 затем отправляет сообщение обратно на ранг 0
  • процесс повторяется N раз

Реализация этой спецификации в C-подобном псевдокоде могла бы быть:

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 раз
  • кроме того, ранг 0 запускает процесс, отправляя его на другой ранг, и завершает процесс, получая одно последнее сообщение из другого ранга.

Возможная реализация в псевдокоде:

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, который затем передает его на ранг 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_rank и next_rank оба равны (rank + 1) % 2, т.е. имеют один и тот же ранг. Кроме того, (rank + 1) % 2 и 1 - rank одинаковы, когда ранг принимает значения, которые либо 0, либо 1.

Я надеюсь, что теперь вы видите мотивацию, стоящую за не жестким кодированием конкретных действий для определенных рангов, а с использованием локальной арифметики для определения того, что делать. В вашем случае каждое значение четного сообщения ping увеличивается на ранг 0, а каждое значение нечетного сообщения ping - на ранг 1, но что, если вы расширите его до кольца рангов? if (rank == ping_value % #ranks) ping_value++; вроде как поступает правильно и работает с любым количеством рангов.

0
Hristo 'away' Iliev 15 Окт 2020 в 12:12