用通俗易懂的英语理解MPI_Allgatherv
Understanding MPI_Allgatherv in plain english
在过去的几周里,我一直在学习如何实现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]
- 用MacOS Mojave编译C++:致命错误:mpi.h:没有这样的文件或目录
- MPI突然停止了对多个核心的操作
- 设置 Visual Studio for MPI: 找不到标识符错误
- 使用 make 编译 MPI,几个命名空间错误,例如"错误:未知类型名称'使用'?
- 如何使用 MPI 的远程内存访问 (RMA) 功能并行化数据聚合?
- 重载 MPI 中的运算符 ()
- MPI:检查是否有任何进程已终止
- 使用 pybind11 共享 MPI 通信器
- 使用 CMake,Microsoft MPI 和 Visual Studio 2017 找不到 mpi.h
- 在具有 MPI 的超立方体中广播
- 通过 mpi 发送 c++ 标准::矢量<bool>
- 使用 MPI 的 C++ 中的并行 for 循环
- 如何将 OpenMP 和 MPI 导入到大型 CLion CMake 项目中?
- 如何通过Boost.MPI发送2d Boost.MultiArray的子阵列?
- HDF5 构建了并行支持,但找不到特定于 mpi 的功能
- MPI 集合通信中的指针分配
- 仅特定内核计数上的 MPI 内存损坏
- 如何在 Mac OS 上安装 boost-mpi 及其对 clang 的依赖关系?
- 从Visual Studio 2017运行MPI应用程序,每个进程在不同的cmd窗口中
- 在 MPI 中共享数组的一部分