【问题标题】:MPI: Canceling Non-blocking SendMPI:取消非阻塞发送
【发布时间】:2011-05-25 05:53:23
【问题描述】:

我正在使用 Open MPI 库来实现以下算法:我们有两个进程 p1p2。他们都在执行一些迭代,并且在每次迭代结束时,他们都会传达他们的结果。问题是执行不一定是平衡的,所以p1 可能在p2 执行1 的时间内执行10 次迭代。尽管如此,我希望p2p1 执行的最后一次迭代中读取最新结果.

因此,我的想法是 p1 在每次迭代时发送其结果。但是,在发送来自迭代i 的结果之前,它应该检查p2 是否真的从迭代i-1 中读取了信息。如果没有,它应该取消之前的发送,这样当p2p1读取时,它会读取最新的结果。

很遗憾,我不知道该怎么做。我尝试过使用 MPI_Cancel,如下代码所示:

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

    int myrank, numprocs;
    MPI_Status status;
    MPI_Request request;

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
    MPI_Comm_rank(MPI_COMM_WORLD, &myrank);

    if(myrank == 0){
        int send_buf = 1, flag;
        MPI_Isend(&send_buf, 1, MPI_INT, 1, 123, MPI_COMM_WORLD, 
                  &request);
        MPI_Cancel(&request);
        MPI_Wait(&request, &status);
        MPI_Test_cancelled(&status, &flag);
        if (flag) printf("Send cancelled\n");
        else printf("Send NOT cancelled\n");
        send_buf = 2;
        MPI_Isend(&send_buf, 1, MPI_INT, 1, 123, MPI_COMM_WORLD, 
                  &request);
    }
    else {
        sleep(5);
        int msg;
        MPI_Recv(&msg, 1, MPI_INT, 0, 123,
                 MPI_COMM_WORLD, &status);
        printf("%d\n", msg);
    }
    MPI_Finalize();

    return 0;
}

但是当我执行时,它说发送无法取消,p2 打印 1 而不是 2。

我想知道是否有任何方法可以实现我的建议,或者是否有其他方法可以对p1p2 之间的行为进行编码。

【问题讨论】:

  • 取消发送是邪恶的,你不应该使用它。并且不能保证您实际上可以取消发送。如果您认为可以通过取消来阻止 isend 远程写入,那么您将感到失望。

标签: c parallel-processing mpi


【解决方案1】:

我会反转对通信的控制。 p1 不会发送它必须取消的不必要的消息,p2 应该表示它已准备好接收消息,而p1 只会在那时发送。与此同时,p1 只是用最新结果覆盖其发送缓冲区。

在(未经测试的)代码中:

if ( rank == 0 )
{
    int ready;
    MPI_Request p2_request;
    MPI_Status p2_status;
    // initial request
    MPI_Irecv(&ready, 1, MPI_INT, 1, 123, MPI_COMM_WORLD, &p2_request);
    for (int i=0; true; i++)
    {
        sleep(1);
        MPI_Test(&p2_request, &ready, &p2_status);
        if ( ready )
        {
            // blocking send: p2 is ready to receive
            MPI_Send(&i, 1, MPI_INT, 1, 123, MPI_COMM_WORLD);
            // post new request
            MPI_Irecv(&ready, 1, MPI_INT, 1, 123, MPI_COMM_WORLD, &p2_request);
        }
    }
}
else
{
    int msg;
    MPI_Status status;
    while (true)
    {
        sleep(5);
        // actual message content doesn't matter, just let p1 know we're ready
        MPI_Send(&msg, 1, MPI_INT, 0, 123, MPI_COMM_WORLD);
        // receive message
        MPI_Recv(&msg, 1, MPI_INT, 0, 123, MPI_COMM_WORLD, &status);
    }
}

现在就像我说的那样,这是未经测试的代码,但您可能会看到我在那儿得到了什么。 MPI_Cancel 只应在出现严重错误时使用:在正常执行期间不应取消任何消息。

【讨论】:

    【解决方案2】:

    另一种方法完全是使用MPI one-sided communications。但是请注意,您在这里真正想要的被动通信是相当棘手的(尽管成对,mpi_win_postmpi_win_start 更容易)并且单方面的东西有望在 MPI-3 中全部改变,所以我不知道我会建议你走多远。

    与您在这里的第一次尝试更直接相关:与其取消消息(如上面所建议的那样,这是非常激烈的),而是通过所有排队的消息可能要容易得多(MPI 保证消息不会相互超越 -唯一需要注意的是,如果您使用 MPI_THREAD_MULTIPLE 并在一个 MPI 任务中发送多个线程,在这种情况下顺序定义不明确):

    #include <stdio.h>
    #include <mpi.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <math.h>
    
    void compute() {
        const int maxusecs=500;
        unsigned long sleepytime=(unsigned long)round(((float)rand()/RAND_MAX)*maxusecs);
    
        usleep(sleepytime);
    }
    
    int main(int argc, char** argv)
    {
      int rank, size, i;
      int otherrank;
      const int niters=10;
      const int tag=5;
      double newval;
      double sentvals[niters+1];
      double othernewval;
      MPI_Request reqs[niters+1];
      MPI_Status stat;
      int ready;
    
      MPI_Init(&argc, &argv);
      MPI_Comm_rank(MPI_COMM_WORLD, &rank);
      MPI_Comm_size(MPI_COMM_WORLD, &size);
      if (size != 2) {
         fprintf(stderr,"This assumes 2 processes\n");
         MPI_Finalize();
         exit(-1);
      }
    
      otherrank = (rank == 0 ? 1 : 0);
      srand(rank);
    
      compute();
      newval = rank * 100. + 0;
      sentvals[0] = newval;
      MPI_Isend(&(sentvals[0]), 1, MPI_DOUBLE, otherrank, tag, MPI_COMM_WORLD, &(reqs[0]));
      MPI_Recv (&othernewval,   1, MPI_DOUBLE, otherrank, tag, MPI_COMM_WORLD, &stat);
      for (i=0; i<niters; i++) {
    
          MPI_Iprobe(otherrank, tag, MPI_COMM_WORLD, &ready, &stat);
          while (ready) {
              MPI_Recv(&othernewval, 1, MPI_DOUBLE, otherrank, tag, MPI_COMM_WORLD, &stat);
              printf("%s[%d]: Reading queued data %lf:\n",
                      (rank == 0 ? "" : "\t\t\t\t"), rank, othernewval);
              MPI_Iprobe(otherrank, tag, MPI_COMM_WORLD, &ready, &stat);
          }
    
          printf("%s[%d]: Got data %lf, computing:\n", 
                  (rank == 0 ? "" : "\t\t\t\t"), rank, othernewval);
          compute();
    
          /* update my data */ 
          newval = rank * 100. + i + 1;
          printf("%s[%d]: computed %lf, sending:\n", 
                  (rank == 0 ? "" : "\t\t\t\t"), rank, newval);
          sentvals[i+1] = newval;
          MPI_Isend(&(sentvals[i+1]), 1, MPI_DOUBLE, otherrank, tag, MPI_COMM_WORLD, &(reqs[0]));
       }
    
    
      MPI_Finalize();
    
      return 0;
    }
    

    运行它会给你(注意,仅仅因为数据被发送并不意味着它在打印时收到):

    [0]: Got data 100.000000, computing:
                                    [1]: Got data 0.000000, computing:
    [0]: computed 1.000000, sending:
    [0]: Got data 100.000000, computing:
                                    [1]: computed 101.000000, sending:
                                    [1]: Got data 0.000000, computing:
    [0]: computed 2.000000, sending:
    [0]: Got data 100.000000, computing:
                                    [1]: computed 102.000000, sending:
                                    [1]: Reading queued data 1.000000:
                                    [1]: Got data 1.000000, computing:
    [0]: computed 3.000000, sending:
    [0]: Reading queued data 101.000000:
    [0]: Got data 101.000000, computing:
                                    [1]: computed 103.000000, sending:
                                    [1]: Reading queued data 2.000000:
                                    [1]: Got data 2.000000, computing:
    [0]: computed 4.000000, sending:
                                    [1]: computed 104.000000, sending:
    [0]: Reading queued data 102.000000:
                                    [1]: Reading queued data 3.000000:
                                    [1]: Got data 3.000000, computing:
    [0]: Got data 102.000000, computing:
    [0]: computed 5.000000, sending:
    [0]: Reading queued data 103.000000:
    [0]: Got data 103.000000, computing:
                                    [1]: computed 105.000000, sending:
                                    [1]: Reading queued data 4.000000:
                                    [1]: Got data 4.000000, computing:
    [0]: computed 6.000000, sending:
    [0]: Reading queued data 104.000000:
    [0]: Got data 104.000000, computing:
                                    [1]: computed 106.000000, sending:
                                    [1]: Reading queued data 5.000000:
                                    [1]: Got data 5.000000, computing:
    [0]: computed 7.000000, sending:
    [0]: Reading queued data 105.000000:
    [0]: Got data 105.000000, computing:
                                    [1]: computed 107.000000, sending:
                                    [1]: Reading queued data 6.000000:
                                    [1]: Got data 6.000000, computing:
    [0]: computed 8.000000, sending:
    [0]: Reading queued data 106.000000:
    [0]: Got data 106.000000, computing:
                                    [1]: computed 108.000000, sending:
                                    [1]: Reading queued data 7.000000:
                                    [1]: Got data 7.000000, computing:
    [0]: computed 9.000000, sending:
    [0]: Reading queued data 107.000000:
    [0]: Got data 107.000000, computing:
                                    [1]: computed 109.000000, sending:
                                    [1]: Reading queued data 8.000000:
                                    [1]: Got data 8.000000, computing:
    [0]: computed 10.000000, sending:
                                    [1]: computed 110.000000, sending:
    

    请注意,这只是演示代码,最终版本真的需要在最后执行 waitalls 和更多 irobes 以释放任何待处理的请求并刷新任何等待的消息。

    【讨论】:

      【解决方案3】:

      您的环境和 MPI 分发是否支持多线程?如果是这样,您可以在 P1 中创建一个线程来计算值并将每次迭代的结果存储在与 P1 的主线程共享的变量中(通过信号量写入保护) 按照上面 suszterpatt 的建议,让 P2 向 P1 发送“我准备好了”消息,并让 P1 用最近一次迭代的值进行响应。

      【讨论】:

        猜你喜欢
        • 2012-08-24
        • 2021-12-30
        • 2012-11-29
        • 1970-01-01
        • 2013-03-07
        • 2014-08-15
        • 2012-02-21
        • 2015-12-09
        • 1970-01-01
        相关资源
        最近更新 更多