3

我有一个希望并行化的串行 C++ 程序。我知道 MPI、 、 等的基础知识MPI_SendMPI_Recv基本上,我有一个数据生成算法,其运行速度明显快于数据处理算法。目前它们串联运行,但我想在根进程中运行数据生成,在从属进程上完成数据处理,然后从根向包含要处理的数据的从属发送消息。这样,每个从站处理一个数据集,然后等待下一个数据集。

问题是,一旦根进程完成生成数据,程序就会挂起,因为从属进程正在等待更多。

这是问题的一个例子:

#include "mpi.h"

#include <cassert>
#include <cstdio>

class Generator {
  public:
    Generator(int min, int max) : value(min - 1), max(max) {}
    bool NextValue() {
      ++value;
      return value < max;
    }
    int Value() { return value; }
  private:
    int value, max;

    Generator() {}
    Generator(const Generator &other) {}
    Generator &operator=(const Generator &other) { return *this; }
};

long fibonnaci(int n) {
  assert(n > 0);
  if (n == 1 || n == 2) return 1;
  return fibonnaci(n-1) + fibonnaci(n-2);
}

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

  int rank, num_procs;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &num_procs);

  if (rank == 0) {
    Generator generator(1, 2 * num_procs);
    int proc = 1;
    while (generator.NextValue()) {
      int value = generator.Value();
      MPI_Send(&value, 1, MPI_INT, proc, 73, MPI_COMM_WORLD);
      printf("** Sent %d to process %d.\n", value, proc);
      proc = proc % (num_procs - 1) + 1;
    }
  } else {
    while (true) {
      int value;
      MPI_Status status;
      MPI_Recv(&value, 1, MPI_INT, 0, 73, MPI_COMM_WORLD, &status);
      printf("** Received %d from process %d.\n", value, status.MPI_SOURCE);
      printf("Process %d computed %d.\n", rank, fibonnaci(2 * (value + 10)));
    }
  }

  MPI_Finalize();
  return 0;
}

显然,并非以上所有内容都是“良好做法”,但足以说明问题。

如果我while(true)从从属进程中删除 ,则程序会在每个从属进程退出时退出。我希望程序仅在根进程完成其工作并且所有从属进程都处理了已发送的所有内容后退出。

如果我知道将生成多少数据集,我可以运行那么多进程并且一切都会很好地退出,但这里不是这样。

有什么建议么?API中有什么可以做到这一点吗?可以通过更好的拓扑更好地解决这个问题吗?会MPI_IsendMPI_IRecv做得更好吗?我对 MPI 还很陌生,所以请耐心等待。

谢谢

4

1 回答 1

9

通常的做法是向所有工作进程发送一条带有特殊标记的空消息,指示它们退出无限处理循环。假设这个标签是 42。你会在工作循环中做类似的事情:

while (true) {
  int value;
  MPI_Status status;
  MPI_Recv(&value, 1, MPI_INT, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
  if (status.MPI_TAG == 42) {
    printf("Process %d exiting work loop.\n", rank);
    break;
  }
  printf("** Received %d from process %d.\n", value, status.MPI_SOURCE);
  printf("Process %d computed %d.\n", rank, fibonnaci(2 * (value + 10)));
}

管理器进程会在生成器循环之后执行以下操作:

for (int i = 1; i < num_procs; i++)
  MPI_Send(&i, 0, MPI_INT, i, 42, MPI_COMM_WORLD);

关于你的下一个问题。MPI_Isend()在主进程中使用会反序列化执行并提高性能。然而,事实是您发送的消息非常小,并且这些消息通常在内部缓冲(警告 - 依赖于实现!),因此您MPI_Send()实际上是非阻塞的,并且您已经具有非串行执行。MPI_Isend()返回MPI_Request您需要稍后处理的句柄。你可以等待它完成,MPI_Wait()或者MPI_Waitall()你也可以打电话MPI_Request_free()操作结束后自动释放。这通常在您想异步发送许多消息并且不关心发送何时完成时完成,但这是一个不好的做法,因为有大量未完成的请求会消耗大量宝贵的内存。至于工作进程 - 他们需要数据才能继续计算,因此MPI_Irecv()不需要使用。

欢迎来到 MPI 编程的精彩世界!

于 2012-05-08T07:04:05.773 回答