假设维度GX*GY*GZ
的全局多维数据集在每个进程中使用3D
笛卡尔拓扑分解为大小为3D
的{{1}}多维数据集。添加Halos以交换数据,这将成为PX*PY*PZ
。 假设我们使用子阵列数据类型进行(PX+2)*(PY+2)*(PZ+2)
晕交换 - 我们是否需要定义2D
子阵列类型?
我的理由是:对于12
平面,我们创建一个用于发送的子阵列类型和一个用于接收的子阵列类型,因为起始坐标将在子阵列数据类型本身中指定。但是有YZ
个平面,这会产生2 YZ
个子阵列数据类型。虽然全局和本地数据大小保持不变但由于起始索引 - 我们需要定义4
个不同的子阵列类型。 使用Vector数据类型发送其中四个平面是否更好?其余两个使用Subarray数据类型?
答案 0 :(得分:5)
您在这里有三个数据访问模式 - 发送/接收子域的X面,Y面和Z面 - 因此您需要三种不同的方式来描述这些图案。您使用哪种类型和多少类型来描述,这在很大程度上取决于您找到表达和使用这些模式的最清晰方式。
让我们假设您在本地PX = 8,PY = 5,PZ = 7,因此包含晕,本地子域为10x7x9。这是在C中,所以我们假设数据存储在一些连续的数组arr[ix][iy][iz]
中,因此前进的值(ix,iy,1)和(ix,iy,2)是连续的(偏移的一个项目大小 - 比如8个字节用于双打),值(ix,1,iz)和(ix,2,iz)偏移(PZ + 2)[即9]值,和(1,iy,iz) )和(2,iy,iz)被(PY + 2)*(PZ + 2)[= 7 * 9 = 63]值偏移。
让我们看看它是如何发挥作用的,勾勒出网格的面,z / y是左/右和上/下,x是相邻面板中显示的。为简单起见,我们在发送/接收的内容中包含角单元格。
您需要向上邻居发送y-face所需的数据如下:
x = 0 x = 1 ... x = 9 Local Grid Size:
+---------+ +---------+ +---------+ PX = 8
6 | | | | | | PY = 5
5 |@@@@@@@@@| |@@@@@@@@@| |@@@@@@@@@| PZ = 7
4 ^| | ^| | ^| |
3 || | || | || |
2 y| | y| | y| |
1 | | | | | |
0 | | | | | |
+---------+ +---------+ +---------+
012345678 012345678 ... 012345678
z-> z-> z->
即,它将从[0] [PY] [0]开始(例如,[0] [5] [0])并延伸到[PX + 1] [PY] [PZ + 1]。所以你从[0] [PY] [0] ... [0] [PY] [PZ + 1]开始,它们是PZ + 2个连续值,然后转到[1] [PY] [0] - 从[0] [PY] [0]跳转(PY + 2)*(PZ + 2)值,开始更早,并取另一个PZ + 2连续值,依此类推。您可以简单地表达为:
它们完全等效,并且没有性能差异。
现在,让我们考虑接收这些数据:
x = 0 x = 1 ... x = 9 Local Grid Size:
+---------+ +---------+ +---------+ PX = 8
6 | | | | | | PY = 5
5 | | | | | | PZ = 7
4 ^| | ^| | ^| |
3 || | || | || |
2 y| | y| | y| |
1 | | | | | |
0 |@@@@@@@@@| |@@@@@@@@@| |@@@@@@@@@|
+---------+ +---------+ +---------+
012345678 012345678 ... 012345678
z-> z-> z->
至关重要的是,所需的数据模式完全相同:PZ + 2值,然后从最后一个块的开头跳过(PY + 2)*(PZ + 2)值,以及另一个PZ + 2值。我们可以将其描述为:
唯一的区别是子阵列类型的子阵列的起始位置。但这并不像看起来那么大的差异!
当您在发送或接收(例如)中实际使用子数组类型时,将例程指针传递给某些数据,然后为其提供具有一些起始位置和切片描述的子数组类型。然后MPI跳到该起始位置,并使用该切片描述的数据布局。
因此,尽管定义和使用四种子阵列类型是完全正确的:
MPI_Type_create_subarray(ndims=3, sizes=[PX+2,PY+2,PZ+2], subsizes=[PX+2,1,PZ+2],
starts=[0,0,0],... &recv_down_yface_t);
MPI_Type_create_subarray(...all the same...
starts=[0,1,0],... &send_down_yface_t);
MPI_Type_create_subarray(...all the same...
starts=[0,PY,0],... &send_up_yface_t);
MPI_Type_create_subarray(...all the same...
starts=[0,PY+1,0],... &recv_up_yface_t);
/* Send lower yface */
MPI_Send(&(arr[0][0][0]), 1, send_down_yface_t, ... );
/* Send upper yface */
MPI_Send(&(arr[0][0][0]), 1, send_up_yface_t, ... );
/* Receive lower face */
MPI_Recv(&(arr[0][0][0]), 1, recv_down_yface_t, ... );
/* Receive upper face */
MPI_Recv(&(arr[0][0][0]), 1, recv_up_yface_t, ... );
声明四个具有不同起点的等效模式,您也可以只定义一个,并使用它指向您需要的数据的不同起点:
MPI_Type_create_subarray(ndims=3, sizes=[PX+2,PY+2,PZ+2], subsizes=[PX+2,1,PZ+2],
starts=[0,0,0],... &yface_t);
/* ... */
/* Send lower yface */
MPI_Send(&(arr[0][1][0]), 1, yface_t, ... );
/* Send upper yface */
MPI_Send(&(arr[0][PY][0]), 1, yface_t, ... );
/* Receive lower face */
MPI_Recv(&(arr[0][0][0]), 1, yface_t, ... );
/* Receive upper face */
MPI_Recv(&(arr[0][PY+1][0]), 1, yface_t, ... );
以上就是您使用相应矢量类型的方式 - 将其指向要发送/接收的第一个项目。
如果您选择使用子阵列类型,使用它的任何一种方式都非常好,并且您将看到在各种软件中做出的两种选择。它只是你更清楚的问题 - 每种模式4种类型(取决于偏移),或在发送/接收中明确使用偏移。我个人认为1-type方法很多更清楚,但是对于那个问题没有明确的正确答案。
至于是否使用MPI_Subarray或Vector(比如说),最简单的方法是查看您需要支持的其他两种模式:使用X面(这里有更多选项,因为他们是连续的:
和z-faces:
所以,这一切都归结为清晰。子阵列类型在所有方向之间看起来最相似,差异相当明显;然而,如果我向你展示了一堆所有在同一段代码中声明的矢量类型,你必须在白板上做一些素描,以确保我没有意外地切换它们。这个子阵列也最容易推广 - 如果你转向一个现在每边需要2个光环单元的方法,或者不发送角单元格,对子阵列的修改是微不足道的,而你必须做一些工作用向量构建东西。