假设一个维度GX*GY*GZ
的全局立方体,在每个进程上使用3D
笛卡尔拓扑将其分解为3D
个大小PX*PY*PZ
的立方体。为数据交换添加光环这将变为(PX+2)*(PY+2)*(PZ+2)
。假设我们使用子阵数据类型进行2D
晕交换-我们需要定义12
子阵类型吗?
我的理由是:对于YZ
平面,我们创建一个用于发送的子阵列类型和一个用于接收的子阵列类型,因为起始坐标将在子阵列数据类型本身中指定。但是存在2 YZ
平面,这导致4
子阵列数据类型。尽管全局和本地数据大小保持不变,但由于起始索引的关系,我们需要定义不同的子数组类型。用矢量数据类型发送四个这样的平面,剩下的两个用子阵列数据类型不是更好吗?
最佳答案
这里有三种数据访问模式—发送/接收子域的X面、Y面和Z面—所以需要三种不同的方式来描述这些模式。你用哪种类型和多少种类型来描述,很大程度上取决于你找到了表达和使用这些模式的最清晰的方式。
假设你有,局部的,px=8,py=5,pz=7,所以包括晕,局部子域是10x7x9。这是在C语言中,所以我们假设数据存储在一个连续数组中,这样(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面所需的数据如下:
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个连续的值,依此类推。可以简单地表示为:
px+2计数、blocklen(pz+2)和步幅(py+2)*(pz+2)的mpi_类型_向量,或
MPI_型_子阵,切片尺寸为[px+2,1,pz+2],从[0,py,0]开始
它们完全相等,没有性能差异。
现在,让我们考虑接收这些数据:
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值。我们可以将其描述为:
px+2计数、blocklen(pz+2)和步幅(py+2)*(pz+2)的mpi_类型_向量,或
MPI_型_子阵,切片尺寸为[px+2,1,pz+2],从[0,0,0]开始
唯一的区别是子阵列类型的子阵列的起始位置。但这并不像看起来的那么大!
当您在发送或接收中实际使用子阵列类型时(比如说),您向例程传递一个指向某些数据的指针,然后给它一个具有某些起始位置和切片描述的子阵列类型。然后,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_u子阵列还是矢量(比方说),最容易看到需要支持的其他两种模式:使用X面(这里还有两个选项,因为它们是连续的:
(PY+2)*(PZ+2)MPI U双打
1 mpi_type_连续的(py+2)*(pz+2)mpi_双倍
mpi_型_向量,计数为1,blocklen(py+2)*(pz+2),步幅为任意数,或计数为py+2,blocklen pz+2,步幅为pz+2,或任何等效组合
一个子数组,切片的子空间为[1,py+2,pz+2],从适当的位置开始
对于z面:
mpi_类型_计数向量(px+2)*(py+2)、块长度1和pz+2的跨距
从适当的位置开始的子数组,其切片的子空间为[px+2,py+2,1]。
所以,这一切又归结为清晰。子数组类型在各个方向上看起来最相似,区别也很明显;但是如果我向您展示了在同一段代码中声明的一组向量类型,您必须在白板上绘制一些草图,以确保我没有意外地切换它们。子阵也最容易推广-如果你移动到一个方法,现在每边需要2个晕细胞,比方说,或不发送角细胞,对子阵的修改是微不足道的,而你必须做一些工作,建立与向量的东西。
关于c - MPI中3D过程分解中交换2D光晕的子数组数据类型的数量,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28182076/