用简单的英语理解MPI_Allgatherv

时间:2013-04-11 14:51:38

标签: c++ mpi

我在过去几周一直在学习如何实施MPI,而且我很难理解如何为MPI_Allgatherv设置一些输入参数。我会用一个玩具的例子,因为我需要在这里采取婴儿步骤。我已经完成的一些研究在本文末尾列出(包括我之前的问题,这引出了我的这个问题)。首先,快速总结一下我想要完成的事情:

- 摘要 - 我采用std :: vector A,让多个处理器在A的不同部分工作,然后获取A的更新部分并将这些更新重新分发到所有处理器。因此,所有处理器都以A的副本开头,更新A的部分,并以完全更新的A副本结束.--结束 -

我们说我有一个std :: vector<双>包含5个名为" mydata"初始化如下:

for (int i = 0; i < 5; i++)
{
    mydata[i] = (i+1)*1.1;
}

现在让我们说我在2个节点上运行我的代码(int tot_proc = 2)。我确定了当前的&#34;节点使用&#34; int id_proc,&#34;因此,根处理器的id_proc = 0.由于mydata中的元素数是奇数,我无法在处理器之间均匀分配工作。让我们说我总是按照以下方式解决问题:

if (id_proc < tot_proc - 1)
{
   //handle mydata.size()/tot_proc elements
}
else
{
   //handle whatever is left over
}

在这个例子中,这意味着: id_proc = 0将适用于mydata [0]和mydata [1](2个元素,因为5/2 = 2)...和... id_proc = 1将适用于mydata [2] - mydata [4](3个元素,自5 / 2 + 5%2 = 3)

一旦每个处理器处理了mydata各自的部分,我想使用Allgatherv将结果合并在一起,以便每个处理器上的mydata包含所有更新的值。我们知道Allgatherv有8个参数:(1)正在发送的元素/数据的 起始地址 ,(2)发送的元素数量,(3)类型发送的数据,在本例中为MPI_DOUBLE,(4)您希望接收数据的位置的地址(未提及&#34;开始&#34;地址),(5)元素的数量是收到,(6)&#34;流离失所&#34;在内存中相对于参数#4中的接收位置,(7)接收的数据类型,再次,MPI_DOUBLE,以及(8)您正在使用的通信器,在我的情况下只是MPI_COMM_WORLD。

现在是混乱开始的地方。由于处理器0处理前两个元素,处理器1处理最后3个元素,因此处理器0将需要发送前两个元素,处理器1将需要发送最后3个元素。对我而言,这表明Allgatherv的前两个论点应该是:

处理器0:MPI_Allgatherv(&amp; mydata [0],2,......

处理器1:MPI_Allgatherv(&amp; mydata [2],3,......

(Q1)我是对的吗?如果是这样,我的下一个问题是关于参数2的格式。让我们说我创建了一个std :: vector&lt; int&gt; sendcount使sendcount [0] = 2,sendcount [1] = 3。

(Q2)Argument 2是否需要引用sendcount的第一个位置,还是需要将引用发送到与每个处理器相关的位置?换句话说,我应该做以下哪些:

Q2 - 选项1

处理器0:MPI_Allgatherv(&amp; mydata [0],&amp; sendcount [0],...

处理器1:MPI_Allgatherv(&amp; mydata [2],&amp; sendcount [0],...

Q2 - 选项2

处理器0:MPI_Allgatherv(&amp; mydata [0],&amp; sendcount [id_proc],...(此处为id_proc = 0)

处理器1:MPI_Allgatherv(&amp; mydata [2],&amp; sendcount [id_proc],...(此处为id_proc = 1)

...参与论证4.由于我正在收集mydata的不同部分,我怀疑这个参数看起来与Argument 1类似。即它应该像&amp; mydata [?]。 (Q3)这个论点可以仅仅是对mydata开头的引用(即&amp; mydata [0]),还是我必须按照我对Argument 1的方式改变索引? (Q4)想象一下,我使用过3个处理器。这意味着处理器1将发送mydata [2]和mydata [3],它们位于&#34;中间&#34;矢量。由于向量的元素是连续的,因此处理器1正在接收的数据必须被分割(一些在之前,而mydata [4]在之后)。我是否必须考虑这个论点中的分歧,如果是,那该怎么办?

......对我来说稍微有点困惑的是Argument 5,但今天早上我有了一个主意。使用玩具示例:如果处理器0正在发送2个元素,那么它将接收3,对吗?类似地,如果处理器1正在发送3个元素,那么它正在接收2.(Q5)因此,如果我要创建一个std :: vector&lt; int&gt; recvcount,我不能把它初始化为:

for (int i = 0; i < tot_proc; i++)
{
    recvcount[i] = mydata.size() - sendcount[i];
}

如果这是真的,那么我将它传递给Allgatherv作为&amp; recvcount [0]或&amp; recvcount [id_proc](类似于Argument 2)?

最后,参数6.我知道这与我对Argument 4的输入有关。我的猜测如下:如果我在所有处理器上将&amp; mydata [0]作为Argument 4传递,那么位移就是数字内存中的位置,我需要移动才能到达实际需要接收数据的第一个位置。例如,

处理器0:MPI_Allgatherv(...,&amp; mydata [0],...,2,...);

处理器1:MPI_Allgatherv(...,&amp; mydata [0],...,0,...);

(Q5)我认为以上两行意味着&#34;处理器0将从位置&amp; mydata [0 + 2]开始接收数据。处理器1将从位置&amp; mydata [0 + 0]开始接收数据。&#34; ??当数据需要像Q4那样分割时会发生什么?最后,由于我正在将一部分向量收集回自身(用更改的mydata替换mydata通过覆盖它),然后这告诉我除根进程以外的所有处理器都将从&amp; mydata [0]开始接收数据。 (Q6)如果这是真的,那么对于不是根的所有处理器,不应该将位移设为0吗?

我读过的一些链接: Difference between MPI_allgather and MPI_allgatherv Difference between MPI_Allgather and MPI_Alltoall functions? Problem with MPI_Gatherv for std::vector C++: Using MPI's gatherv to concatenate vectors of differing lengths http://www.mcs.anl.gov/research/projects/mpi/www/www3/MPI_Allgatherv.html https://computing.llnl.gov/tutorials/mpi/#Routine_Arguments

我之前关于stackoverflow的帖子: MPI C++ matrix addition, function arguments, and function returns

我读过的大多数教程等都只是掩盖了Allgatherv。

1 个答案:

答案 0 :(得分:2)

这里令人困惑的部分原因是你正在尝试进行就地聚集;您正尝试从同一阵列发送和接收。如果您这样做,则应使用MPI_IN_PLACE选项,在这种情况下,您不必明确指定发送位置或计数。如果您从不同的缓冲区发送信息而不是您接收到的信息,则可以使用这些信息,但就地收集会受到更多限制。

这样可行:

#include <iostream>
#include <vector>
#include <mpi.h>

int main(int argc, char **argv) {
    int size, rank;

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

    if (size < 2) {
        std::cerr << "This demo requires at least 2 procs." << std::endl;
        MPI_Finalize();
        return 1;
    }

    int datasize = 2*size + 1;
    std::vector<int> data(datasize);

    /* break up the elements */
    int *counts = new int[size];
    int *disps  = new int[size];

    int pertask = datasize/size;
    for (int i=0; i<size-1; i++)
        counts[i] = pertask;
    counts[size-1] = datasize - pertask*(size-1);

    disps[0] = 0;
    for (int i=1; i<size; i++)
        disps[i] = disps[i-1] + counts[i-1];

    int mystart = disps[rank];
    int mycount = counts[rank];
    int myend   = mystart + mycount - 1;

    /* everyone initialize our data */
    for (int i=mystart; i<=myend; i++)
        data[i] = 0;

    int nsteps = size;
    for (int step = 0; step < nsteps; step++ ) {

        for (int i=mystart; i<=myend; i++)
            data[i] += rank;

        MPI_Allgatherv(MPI_IN_PLACE, 0, MPI_DATATYPE_NULL,
                       &(data[0]), counts, disps, MPI_INT, MPI_COMM_WORLD);

        if (rank == step) {
            std::cout << "Rank " << rank << " has array: [";
            for (int i=0; i<datasize-1; i++)
                std::cout << data[i] << ", ";
            std::cout << data[datasize-1] << "]" << std::endl;
        }
    }

    delete [] disps;
    delete [] counts;

    MPI_Finalize();
    return 0;
}

跑步给出

$ mpirun -np 3 ./allgatherv
Rank 0 has array: [0, 0, 1, 1, 2, 2, 2]
Rank 1 has array: [0, 0, 2, 2, 4, 4, 4]
Rank 2 has array: [0, 0, 3, 3, 6, 6, 6]