来自未知来源的MPI异步广播

我有一个C项目,有n个处理器在进行一种树搜索。 在程序的任何给定时间,这些过程中的任何一个都可能找到感兴趣的东西并且想要异步地将其发送到所有其他处理器。

如何在其他进程中侦听新消息,而无需在每次循环迭代时循环遍历所有可能的发送方?

我已经阅读了有关此问题的其他问题,例如这个问题( MPI – 异步广播/收集 ),但是,到目前为止我所看到的所有问题都没有处理不可预测的发送者或循环每个可能的发送者,我实际上并不是这样。花哨。

编辑澄清:

将找到的值发送到根级别并从那里分发它是不可选的。 如果我没有那个条件,祖兰的答案会起作用,所以对于其他人来说,这可能会有所帮助。 在我的情况下,它可以(并且肯定会)发生不同的排名找到他们需要多次共享的东西(这意味着竞争条件也可能发生)。

经过大量的试验和错误(以及其他反馈)后,我发现性能最佳的解决方案使用异步广播。

我的第一种方法受到Petru的回答的启发,使用多个非阻塞MPI_Isend()调用来分发数据,并且只有一个MPI_Irecv()调用(使用任何源)来定期接收数据。 这种方法结果非常慢,因为非阻塞MPI_ISend()调用必须由发送方在创建的每个MPI_Request句柄上使用MPI_Test()进行检查。 原因是,异步操作在后台工作的意义上并非真正异步,而是必须定期检查。

在我的情况下,该问题还提出了可以(并且将)多次找到新解决方案的可能性,这导致现有的,开放的MPI_Request句柄的许多管理问题,其必须等待或以其他方式管理。

我的第二种方法是使用Zulan提出的中央沟通者。 当没有多少消息时,这种方法非常快,但是当同时有许多找到的解决方案时,根级别被阻塞,在特殊情况下使其非常慢。 最重要的是,根级别不再像其他人那样快(这是可以预期的),这导致我的案例总体上更慢。

我的第三个也是最后一个方法是使用多个非阻塞MPI_Ibcast()调用,每个调用可以发送一个消息。 例如,这意味着在具有3个不同等级的情况下

  • rank 0有两个开放的MPI_Ibcast,根1和2
  • rank 1有两个开放的MPI_Ibcast,根0和2
  • rank 2有两个开放的MPI_Ibcast,根0和1

当排名然后找到解决方案时,它会将最后一个必要的MPI_Ibcast作为根发布。 首先,这似乎与方法一类似,但是,在这种情况下,发送方始终只需要监视单个请求句柄(来自最终MPI_Ibcast的句柄)并使用MPI_Test()定期检查它。 其他排名始终具有相同数量的开放广播(即世界大小减1),可以存储在数组中并使用MPI_Testany()进行检查。 然而,这种方法的困难在于每个开放广播必须在它自己的通信器上运行,基本上需要与排名一样多的通信器。

因此, 我的发现是第二种方法有时是可以接受的,当没有很多消息时,它是一种快速可行的选择。 这是最容易处理和实施的。 第三种方法在较重负载下更快,并且使我的程序终止非常容易,因为始终存在已知量的开放连接。 实现和使用比其他实现更多的内存是最难和最长的。 (我不知道这个的原因,但它似乎与广播的缓冲管理以及可能的其他传播者有关。)最后我不能强调第一种方法起初看起来很容易,但是当你真的想要为了跟踪每一个请求,不幸的是,它很难管理和减速。

您可以选择根级别,并将异步发送与源自此根的异步广播相结合。 这是一个小例子:

#include  #include  int main() { int size, rank; MPI_Init(NULL, NULL); MPI_Comm_size(MPI_COMM_WORLD, &size); MPI_Comm_rank(MPI_COMM_WORLD, &rank); const int root = 0; const int found_tag = 42; const long long chunk = 100000ll; const long long target = 134861523ll; MPI_Request found_request; long long found_item; if (rank == root) { MPI_Irecv(&found_item, 1, MPI_LONG_LONG, MPI_ANY_SOURCE, found_tag, MPI_COMM_WORLD, &found_request); } else { MPI_Ibcast(&found_item, 1, MPI_LONG_LONG, root, MPI_COMM_WORLD, &found_request); } for (long long my_number = rank;; my_number += size) { if (my_number == target) { found_item = my_number; printf("I found it: %d, %lld\n", rank, found_item); // We don't stop here, we will continue until the root tells us to stop // This also works if we are the root MPI_Send(&my_number, 1, MPI_LONG_LONG, root, found_tag, MPI_COMM_WORLD); } // Avoid checking MPI_Test too often. if ((1 + (my_number / size)) % chunk == 0) { if (rank == root) { int found = 0; MPI_Test(&found_request, &found, MPI_STATUS_IGNORE); if (found) { printf("Someone told me that he found it: %d, %lld\n", rank, found_item); MPI_Request bcast_request; MPI_Ibcast(&found_item, 1, MPI_LONG_LONG, root, MPI_COMM_WORLD, &bcast_request); MPI_Wait(&bcast_request, MPI_STATUS_IGNORE); break; } } else { int found = 0; MPI_Test(&found_request, &found, MPI_STATUS_IGNORE); if (found) { break; } } } } printf("%d, %lld\n", rank, found_item); MPI_Finalize(); } 

此代码假定您只找到一个数字 – 一次。 查找多个数字将需要一些额外的代码。

如您所述,您无法发布包含未知发件人的广播。 现在你可以发布一个MPI_Allreduce (甚至是MPI_Allgather ) – 之后所有等级都会知道找到的值。 但是,您不能只发布一次异步 – 因为您在发布后无法更改该值。

首先,作为其他集合原语,MPI广播操作需要所有进程参与,这意味着如果要使用集合操作,则需要所有进程才能输入语句。 这是因为MPI广播原语在同一集合语句中执行发送和接收: MPI_Bcast的相应接收例程

这种抽象通常允许集体的非天真实现,所有过程实际上都可以为广播做出贡献。 这里有很好的解释: http : //mpitutorial.com/tutorials/mpi-broadcast-and-collective-communication/

如果您希望异步通知所找到的每个值,因为它可能以某种方式为您的算法做出贡献,使用异步发送循环每个进程可能是您最好的选择,特别是如果这些值很小。

在您的问题中,您似乎只关心使用循环来监听消息。 请注意,您可以通过使用MPI_ANY_SOURCE作为探测和接收消息的源参数来避免接收循环。 这样可以避免循环,但只能探测/接收一条消息,这取决于你想要做什么,你可能想要循环,直到队列中没有更多消息,或者有更复杂的东西来防止大量消息阻止进展处理。

您可以通过定期出现的同步点发送值来逃避。 您可以延迟交付并在本地收集数字,直到出现下一个同步点,而不是立即发送找到的号码。 那时,你一次发出一堆数字。 当您一次发送一堆时,可能需要一些开销来创建同步点。

可以使用all创建同步点。 每个进程将其本地收集的号码的数量发送给所有其他号码。 allgather就像一个障碍,在此之后你可以进行实际的广播。 在此障碍之后,所有进程都知道广播的大小以及它们包含的项目数量。

Allgather也只发送号码,为什么不马上发送你的号码呢? 因为allgather操作可能相对“空”(如果你知道我的意思……),并且其他进程在发生时不知道时间点。 如果您选择固定同步点,则每个人都知道何时进行同步,并且可能要传输多个号码。

另一种选择可能是研究MPI-3的RMA(远程存储器操作)。 它们也被称为“单侧”,因为接收器不需要调用相应的接收。 因此,您无需知道根目录即可接收广播数据。 也许你可以使用这些操作构建一个智能的put / get方案。

我没有为自己找到一个很好的解决方案,仍然有类似的问题。 到目前为止我最好的想法:在每个进程中为每个可能的广播根启动一个广播监听器(=线程)。 当然,这会产生很大的开销:您需要一个线程,并且每个bcast源必须使用自己的MPI_Communicator。 监听器将接收到的数据放在一个公共消息队列中,然后就可以了。