【问题标题】:Understanding MPI_Allgatherv in plain english用简单的英语理解 MPI_Allgatherv
【发布时间】:2016-12-20 10:51:50
【问题描述】:

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

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

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

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,…

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

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

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

Q2 - 选项 1

处理器 0:MPI_Allgatherv(&mydata[0], &sendcount[0],…

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

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 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吗?

我读过的一些链接: 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。

【问题讨论】:

    标签: c++ mpi


    【解决方案1】:

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

    【讨论】:

    • 我根据我的代码调整了您的建议,并且运行良好!感谢您的帮助!
    • 总是在 Allgatherv 中明确指定计数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-12
    • 2012-09-02
    • 1970-01-01
    • 2021-10-14
    • 2014-05-20
    • 1970-01-01
    相关资源
    最近更新 更多