什么是“通知”处理器而不阻塞的正确方法?

时间:2016-01-31 04:50:12

标签: mpi

假设我有很多东西,我必须对所有这些事情进行一些操作。 如果一个元素的操作失败,我想在所有数组中停止工作[这项工作分布在多个处理器]。

我希望在保持发送/接收消息数量最少的同时实现这一目标。 另外,如果不需要,我不想阻止处理器。

如何使用MPI进行操作?

3 个答案:

答案 0 :(得分:1)

以非阻塞方式推导此全局停止条件的可能策略是依赖MPI_Test

场景

考虑每个进程将一个类型为MPI_INT的异步接收发布到其左侧排名,并使用给定标记构建一个环。然后开始计算。如果等级遇到停止条件,则将其自己的等级发送到其右等级。同时,每个等级使用MPI_Test来检查计算期间MPI_Irecv的完成情况,如果它已完成,则先输入分支,然后等待消息,然后将接收到的等级传递到右边,除非是正确的等级等于消息的有效负载(不是循环)。

这样就完成了你应该在分支中拥有所有进程,准备触发任意恢复操作。

复杂性

保留的拓扑是一个环,因为它最多可以最小化消息数(n-1),但它会增加传播时间。其他拓扑可以保留更多消息,但空间复杂度较低,例如具有n.ln(n)复杂度的树。

实施

像这样。

int rank, size;
MPI_Init(&argc,&argv);
MPI_Comm_rank( MPI_COMM_WORLD, &rank);
MPI_Comm_size( MPI_COMM_WORLD, &size);

int left_rank = (rank==0)?(size-1):(rank-1);
int right_rank = (rank==(size-1))?0:(rank+1)%size;

int stop_cond_rank;
MPI_Request stop_cond_request;
int stop_cond= 0;

while( 1 )
{
         MPI_Irecv( &stop_cond_rank, 1, MPI_INT, left_rank, 123, MPI_COMM_WORLD, &stop_cond_request);

         /* Compute Here and set stop condition accordingly */

         if( stop_cond )
         {
                 /* Cancel the left recv */
                 MPI_Cancel( &stop_cond_request );
                 if( rank != right_rank )
                            MPI_Send( &rank, 1, MPI_INT, right_rank, 123, MPI_COMM_WORLD ); 

                   break;
         }

         int did_recv = 0;
         MPI_Test( &stop_cond_request, &did_recv, MPI_STATUS_IGNORE );
         if( did_recv )
         {
                  stop_cond = 1;
                  MPI_Wait( &stop_cond_request, MPI_STATUS_IGNORE );
                  if( right_rank != stop_cond_rank )
                            MPI_Send( &stop_cond_rank, 1, MPI_INT, right_rank, 123, MPI_COMM_WORLD );

                   break;
          }
}

if( stop_cond )
{
      /* Handle the stop condition */
}
else
{
      /* Cleanup */
     MPI_Cancel( &stop_cond_request );
}

答案 1 :(得分:1)

这似乎是一个常见的问题,没有简单的答案。其他答案都有可扩展性问题。环通信方法具有线性通信成本,而在片面MPI_Win - 解决方案中,单个进程将受到来自所有工作者的内存请求的打击。这可能适用于较少数量的排名,但在增加排名时会出现问题。

非阻塞集体可以提供更具可扩展性的更好解决方案。主要思想是在所有等级上发布MPI_Ibarrier,除了一个指定的根。此根将通过MPI_Irecv收听点对点停止消息,并在收到后完成MPI_Ibarrier

棘手的部分是有四种不同的情况需要处理“{root,non-root} x {found,not-found}”。还有可能发生多个排名发送停止消息,需要在根上接收未知数量的匹配。这可以通过额外减少来解决,该减少计算发送停止请求的等级数。

这是一个如何在C中看到的例子:

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

const int iter_max = 10000;
const int difficulty = 20000;

int find_stuff()
{
    int num_iters = rand() % iter_max;
    for (int i = 0; i < num_iters; i++) {
        if (rand() % difficulty == 0) {
            return 1;
        }
    }
    return 0;
}

const int stop_tag = 42;
const int root = 0;

int forward_stop(MPI_Request* root_recv_stop, MPI_Request* all_recv_stop, int found_count)
{
    int flag;
    MPI_Status status;
    if (found_count == 0) {
        MPI_Test(root_recv_stop, &flag, &status);
    } else {
        // If we find something on the root, we actually wait until we receive our own message.
        MPI_Wait(root_recv_stop, &status);
        flag = 1;
    }
    if (flag) {
        printf("Forwarding stop signal from %d\n", status.MPI_SOURCE);
        MPI_Ibarrier(MPI_COMM_WORLD, all_recv_stop);
        MPI_Wait(all_recv_stop, MPI_STATUS_IGNORE);
        // We must post some additional receives if multiple ranks found something at the same time
        MPI_Reduce(MPI_IN_PLACE, &found_count, 1, MPI_INT, MPI_SUM, root, MPI_COMM_WORLD);
        for (found_count--; found_count > 0; found_count--) {
            MPI_Recv(NULL, 0, MPI_CHAR, MPI_ANY_SOURCE, stop_tag, MPI_COMM_WORLD, &status);
            printf("Additional stop from: %d\n", status.MPI_SOURCE);
        }
        return 1;
    }
    return 0;
}

int main()
{
    MPI_Init(NULL, NULL);

    int rank;
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    srand(rank);

    MPI_Request root_recv_stop;
    MPI_Request all_recv_stop;
    if (rank == root) {
        MPI_Irecv(NULL, 0, MPI_CHAR, MPI_ANY_SOURCE, stop_tag, MPI_COMM_WORLD, &root_recv_stop);
    } else {
        // You may want to use an extra communicator here, to avoid messing with other barriers
        MPI_Ibarrier(MPI_COMM_WORLD, &all_recv_stop);
    }

    while (1) {
        int found = find_stuff();
        if (found) {
            printf("Rank %d found something.\n", rank);
            // Note: We cannot post this as blocking, otherwise there is a deadlock with the reduce
            MPI_Request req;
            MPI_Isend(NULL, 0, MPI_CHAR, root, stop_tag, MPI_COMM_WORLD, &req);
            if (rank != root) {
                // We know that we are going to receive our own stop signal.
                // This avoids running another useless iteration
                MPI_Wait(&all_recv_stop, MPI_STATUS_IGNORE);
                MPI_Reduce(&found, NULL, 1, MPI_INT, MPI_SUM, root, MPI_COMM_WORLD);
                MPI_Wait(&req, MPI_STATUS_IGNORE);
                break;
            }
            MPI_Wait(&req, MPI_STATUS_IGNORE);
        }
        if (rank == root) {
            if (forward_stop(&root_recv_stop, &all_recv_stop, found)) {
                break;
            }
        } else {
            int stop_signal;
            MPI_Test(&all_recv_stop, &stop_signal, MPI_STATUS_IGNORE);
            if (stop_signal)
            {
                MPI_Reduce(&found, NULL, 1, MPI_INT, MPI_SUM, root, MPI_COMM_WORLD);
                printf("Rank %d stopping after receiving signal.\n", rank);
                break;
            }
        }
    };

    MPI_Finalize();
}

虽然这不是最简单的代码,但它应该:

  • 不会引入其他阻止
  • 通过实施障碍(通常为O(log N)
  • 进行扩展
  • 从发现所有停止的2 *循环时间(+ 1 p2p + 1障碍+ 1减少)的最坏情况延迟。
  • 如果很多/所有级别同时找到解决方案,它仍然有效,但可能效率较低。

答案 2 :(得分:0)

这个问题我曾经多次问自己没有找到任何完全令人满意的答案......我唯一想到的是(MPI_Abort()旁边的那个,但有点极端)是创建一个MPI_Win存储一个标志,该标志将由面临问题的任何进程引发,并定期检查所有进程以查看它们是否可以继续处理。这是使用非阻塞调用完成的,方法与this answer中描述的方法相同。

这方面的主要缺点是:

  1. 这取决于自愿检查标志状态的过程。他们的工作没有立即中断通知他们。
  2. 必须手动调整此检查的频率。您必须在浪费处理数据之间找到权衡,而不需要因为其他地方发生的事情,以及检查状态所需的时间...
  3. 最后,我们需要的是一种定义由MPI调用触发的回调操作的方法,例如MPI_Abort()(基本上用其他东西替换中止操作)。我不认为这存在,但也许我忽略了它。