MPI:当预期的MPI_Recv数量未知时该怎么办

5
我有许多从节点,它们可能会或可能不会向主节点发送消息。因此,目前没有办法让主节点知道要期望多少个MPI_Recv。出于效率原因,从节点必须向主节点发送最少量的消息。
我设法找到了一个很酷的技巧,当不再期望任何消息时,它会发送一个额外的“完成”消息。不幸的是,在我的情况下,由于发送者数量不定,它似乎无法正常工作。您有什么想法吗?谢谢!
if(rank == 0){ //MASTER NODE

    while (1) {

        MPI_Recv(&buffer, 10, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);

        if (status.MPI_TAG == DONE) break;


        /* Do stuff */
    }

}else{ //MANY SLAVE NODES

    if(some conditions){
        MPI_Send(&buffer, 64, MPI_INT, root, 1, MPI_COMM_WORLD);
    }

}


MPI_Barrier(MPI_COMM_WORLD);
MPI_Send(NULL, 1, MPI_INT, root, DONE, MPI_COMM_WORLD);

程序不工作,似乎仍在等待MPI_Recv


刚试了一下,我认为MPI_Barrier正在执行,但是“在屏障之后”消息从未被打印出来,因为程序在MPI_Recv处被卡住了。 - kornesh
排名为0的进程从未调用屏障,因此它显然会挂起。移除屏障后,它将运行。 - Jeff Hammond
它实际上在没有停止的情况下运行,但是在第一个从节点完成计算后立即执行了MPI_Send(NULL, 1, MPI_INT, root, DONE, MPI_COMM_WORLD);,而不等待其他从节点。 - kornesh
尝试将 MPI_Barrier(MPI_COMM_WORLD); 移动到 else 子句内,仍然在 MPI_Recv 处挂起。 - kornesh
您不能在 comm 子集上调用 Barrier。请创建由工作进程组成的子 comm,或以其他方式同步它们。 - Jeff Hammond
此外,每个进程都会向根节点发送“完成”信号,包括根节点自身。其他进程调用Send方法时将无法匹配。您应该制定算法方案并考虑细节,而不仅仅是调整代码。 - Jeff Hammond
2个回答

4
更简单、更优雅的选择是使用 MPI_IBARRIER。让每个工作进程执行它需要的所有发送操作,然后在完成后调用 MPI_IBARRIER。在主进程中,可以循环执行 MPI_IRECVMPI_IBARRIER。当 MPI_IBARRIER 完成时,您就知道每个进程都已经完成,可以取消 MPI_IRECV 并继续执行。伪代码如下所示:
if (master) {
  /* Start the barrier. Each process will join when it's done. */
  MPI_Ibarrier(MPI_COMM_WORLD, &requests[0]);

  do {
    /* Do the work */
    MPI_Irecv(..., MPI_ANY_SOURCE, &requests[1]);

    /* If the index that finished is 1, we received a message.
     * Otherwise, we finished the barrier and we're done. */
    MPI_Waitany(2, requests, &index, MPI_STATUSES_IGNORE);
  } while (index == 1);

  /* If we're done, we should cancel the receive request and move on. */
  MPI_Cancel(&requests[1]);
} else {
  /* Keep sending work back to the master until we're done. */
  while( ...work is to be done... ) {
    MPI_Send(...);
  }

  /* When we finish, join the Ibarrier. Note that
   * you can't use an MPI_Barrier here because it
   * has to match with the MPI_Ibarrier above. */
  MPI_Ibarrier(MPI_COMM_WORLD, &request);
  MPI_Wait(&request, MPI_STATUS_IGNORE);
}

1

1- 你在错误的地方调用了MPI_Barrier,应该在MPI_Send之后调用。
2- 当根进程(rank为0)从所有其他进程(大小为size-1)接收到DONE时,它将退出循环。

经过一些修改后的代码:

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

int main(int argc, char** argv)
{

    MPI_Init(NULL, NULL);
    int size;
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    int rank;
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Status status;
    int DONE = 888;
    int buffer = 77;
    int root = 0 ;
    printf("here is rank %d with size=%d\n" , rank , size);fflush(stdout);
    int num_of_DONE = 0 ;
 if(rank == 0){ //MASTER NODE


    while (1) {

        MPI_Recv(&buffer, 1, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
        printf("root recev %d from %d with tag = %d\n" , buffer , status.MPI_SOURCE , status.MPI_TAG );fflush(stdout);

        if (status.MPI_TAG == DONE)
        num_of_DONE++;
    printf("num_of_DONE=%d\n" , num_of_DONE);fflush(stdout);
    if(num_of_DONE == size -1)
        break;



        /* Do stuff */
    }

}else{ //MANY SLAVE NODES

    if(1){
        buffer = 66;
        MPI_Send(&buffer, 1, MPI_INT, root, 1, MPI_COMM_WORLD);
        printf("rank %d sent data.\n" , rank);fflush(stdout);
    }

}

    if(rank != 0)
    {
        buffer = 55;
        MPI_Send(&buffer, 1, MPI_INT, root, DONE, MPI_COMM_WORLD);
    }


    MPI_Barrier(MPI_COMM_WORLD);
    printf("rank %d done.\n" , rank);fflush(stdout);
    MPI_Finalize();
    return 0;
}

输出:
    hosam@hosamPPc:~/Desktop$ mpicc -o aa aa.c
    hosam@hosamPPc:~/Desktop$ mpirun -n 3 ./aa
here is rank 2 with size=3
here is rank 0 with size=3
rank 2 sent data.
here is rank 1 with size=3
rank 1 sent data.
root recev 66 from 1 with tag = 1
num_of_DONE=0
root recev 66 from 2 with tag = 1
num_of_DONE=0
root recev 55 from 2 with tag = 888
num_of_DONE=1
root recev 55 from 1 with tag = 888
num_of_DONE=2
rank 0 done.
rank 1 done.
rank 2 done.

我刚刚尝试了你的方法,不幸的是它比预期提前终止了。每个从节点需要自己的时间来完成计算,网格中的最后一个节点并不意味着它应该最后终止。如果最后一个进程计算时间少于1秒,我认为sleep(1)就可以解决问题了。 - kornesh
@kornesh:我修改了答案。 - houssam
这是一种优雅的方式,但出于效率原因,从属节点必须向主节点发送最少数量的消息。 - kornesh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接