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

时间:2017-05-15 07:17:20

标签: c asynchronous mpi communication broadcast

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

如何在不必每次循环迭代的情况下遍历所有可能的发件人的情况下,在其他进程上侦听新消息?

我已经阅读了有关此问题的其他问题,例如此问题(MPI - Asynchronous Broadcast/Gather),但是,到目前为止,我所看到的任何问题都无法处理不可预测的发件人或循环每个可能的发件人,我不太喜欢。

编辑澄清:

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

4 个答案:

答案 0 :(得分:2)

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

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

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

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

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

  • 排名0有两个开放的MPI_Ibcasts,根1和2
  • 排名1有两个开放的MPI_Ibcast,根0和2
  • 排名2有两个开放的MPI_Ibcast,根0和1

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

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

答案 1 :(得分:0)

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

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

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) - 之后所有排名都会知道找到的值。但是,您不能只发布一次异步 - 因为您在发布后无法更改该值。

答案 2 :(得分:0)

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

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

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

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

答案 3 :(得分:0)

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

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

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

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

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