3

在过去的几周里,我一直在学习如何实现 MPI,我很难理解如何为 MPI_Allgatherv 设置一些输入参数。我将使用一个玩具示例,因为我需要在这里采取一些小步骤。我所做的一些研究列在这篇文章的末尾(包括我之前的问题,这导致了我这个问题)。首先,快速总结一下我要完成的工作:

--总结--我正在使用一个 std::vector A,让多个处理器在 A 的不同部分上工作,然后获取 A 的更新部分并将这些更新重新分配给所有处理器。因此,所有处理器都以 A 的副本开始,更新 A 的部分,并以完全更新的 A 副本结束。--结束--

假设我有一个包含 5 个名为“mydata”的元素的 std::vector < double >,初始化如下:

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

现在假设我在 2 个节点上运行我的代码(int tot_proc = 2)。我使用“int id_proc”识别“当前”节点,因此,根处理器的 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 )您希望接收数据的位置的地址(没有提及“起始”地址),(5)正在接收的元素数量,(6)内存中相对于参数中接收位置的“位移” 4,(7)接收的数据类型,再次,MPI_DOUBLE,以及(8)你正在使用的通信器,在我的例子中就是 MPI_COMM_WORLD。

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

处理器 0:MPI_Allgatherv(&mydata[0],2,…</p>

处理器 1:MPI_Allgatherv(&mydata[2],3,…

(Q1)我说得对吗?如果是这样,我的下一个问题是关于参数 2 的格式。假设我创建了一个 std::vector < int > sendcount,使得 sendcount[0] = 2 和 sendcount[1] = 3。

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

Q2 - 选项 1

处理器 0:MPI_Allgatherv(&mydata[0], &sendcount[0],…</p>

处理器 1:MPI_Allgatherv(&mydata[2], &sendcount[0],…</p>

Q2 - 选项 2

处理器 0: MPI_Allgatherv(&mydata[0], &sendcount[id_proc], ... (这里 id_proc = 0)

处理器 1:MPI_Allgatherv(&mydata[2], &sendcount[id_proc], ... (这里 id_proc = 1)

...关于论点 4。由于我将 mydata 的不同部分收集回自身,我怀疑这个论点看起来与论点 1 相似。即它应该类似于 &mydata[?]。(Q3)这个参数可以简单地作为对 mydata 开头的引用(即 &mydata[0]),还是我必须像对参数 1 所做的那样更改索引?(Q4) 想象一下我使用了 3 个处理器。这意味着处理器 1 将发送位于向量“中间”的 mydata[2] 和 mydata[3]。由于向量的元素是连续的,因此必须拆分处理器 1 接收的数据(一些在前面,而 ​​mydata[4] 在后面)。我是否必须在这个论点中解释这种分裂,如果是这样,如何解释?

...对我来说更令人困惑的是论点 5,但今天早上我有了一个想法。使用玩具示例:如果处理器 0 发送 2 个元素,那么它将接收 3 个元素,对吗?同样,如果处理器 1 发送 3 个元素,那么它接收 2 个元素。(Q5)所以,如果我要创建一个 std::vector < int > recvcount,我不能将它初始化为:

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

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

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

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

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

(Q5) 我是否正确地认为上述两行的意思是“处理器 0 将接收从位置 &mydata[0+2] 开始的数据。处理器 1 将接收从位置 &mydata[0+0] 开始的数据”。?? 当需要像第四季度那样拆分数据时会发生什么?最后,由于我将向量的一部分收集回自身(通过覆盖它来用更新的 mydata 替换 mydata),因此这告诉我除根进程之外的所有处理器都将从 &mydata[0] 开始接收数据。(Q6)如果这是真的,那么对于所有不是根的处理器来说,位移不应该是 0 吗?

我读过的一些链接: MPI_allgather 和 MPI_allgatherv 之间的区别 MPI_Allgather 和 MPI_Alltoall 函数之间的区别? std::vector C++ 的 MPI_Gatherv 问题:使用 MPI 的gatherv 连接不同长度的向量 http://www.mcs.anl.gov/research/projects/mpi/www/www3/MPI_Allgatherv.html https://computing。 llnl.gov/tutorials/mpi/#Routine_Arguments

我之前关于 stackoverflow 的帖子: MPI C++ 矩阵加法、函数参数和函数返回

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

4

1 回答 1

3

这里的部分困惑是您正在尝试进行就地收集。您正在尝试从同一个数组发送和接收。如果您这样做,则应使用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]
于 2013-04-11T15:53:35.860 回答