用通俗易懂的英语理解MPI_Allgatherv

Understanding MPI_Allgatherv in plain english

本文关键字:MPI Allgatherv 通俗易懂 英语      更新时间:2023-10-16

在过去的几周里,我一直在学习如何实现MPI,我很难理解如何为MPI_Allgatherv设置一些输入参数。我将用一个玩具的例子,因为我需要在这里迈出小步。我所做的一些研究列在这篇文章的末尾(包括我之前的问题,这让我提出了这个问题)。首先,简要总结一下我正在努力实现的目标:

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

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

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

现在假设我在2个节点上运行代码(inttot_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<int>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<int>recvcount,难道我不能将其初始化为:吗

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

如果这是真的,那么我把它作为&recvcount[0]或&recvcount[id_proc](类似于参数2)?

最后,论点6。我知道这与我对参数4的输入有关。我的猜测是:如果我通过&mydata[0]作为所有处理器上的参数4,则位移是内存中需要移动的位置数,以便到达实际需要接收数据的第一个位置。例如,

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

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

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

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

我以前在stackoverflow上的帖子:MPI C++矩阵添加、函数参数和函数返回

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

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