C MPI中3D过程分解中用于交换2D晕的子阵列数据类型数

C MPI中3D过程分解中用于交换2D晕的子阵列数据类型数,c,3d,parallel-processing,2d,mpi,C,3d,Parallel Processing,2d,Mpi,假设一个维度为GX*GY*GZ的全局立方体,该立方体使用3D笛卡尔拓扑分解为3D大小为PX*PY*PZ的立方体。为数据交换添加光环这将成为(PX+2)*(PY+2)*(PZ+2)。假设我们为2Dhalo交换使用子数组数据类型-我们需要定义12子数组类型吗 我的推理是:对于YZ平面,我们创建一个用于发送的子阵列类型和一个用于接收的子阵列类型,因为起始坐标将在子阵列数据类型本身中指定。但是存在2yz平面,这导致4子阵列数据类型。尽管全局和局部数据大小保持不变,但由于起始索引的原因,我们需要定义不同的

假设一个维度为
GX*GY*GZ
的全局立方体,该立方体使用
3D
笛卡尔拓扑分解为
3D
大小为
PX*PY*PZ
的立方体。为数据交换添加光环这将成为
(PX+2)*(PY+2)*(PZ+2)
。假设我们为
2D
halo交换使用子数组数据类型-我们需要定义
12
子数组类型吗


我的推理是:对于
YZ
平面,我们创建一个用于发送的子阵列类型和一个用于接收的子阵列类型,因为起始坐标将在子阵列数据类型本身中指定。但是存在
2yz
平面,这导致
4
子阵列数据类型。尽管全局和局部数据大小保持不变,但由于起始索引的原因,我们需要定义不同的子数组类型。使用矢量数据类型发送其中四个平面,使用子阵列数据类型发送其余两个平面,不是更好吗

这里有三种数据访问模式-发送/接收子域的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面发送到上邻居所需的数据如下所示:

       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][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_Type_子阵列,切片子基为[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_Type_子阵列,片子基为[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_子阵列还是矢量(例如),最简单的方法是查看您需要支持的其他两种模式:使用X面(这里您有更多的选项,因为它们是连续的:

  • (PY+2)*(PZ+2)MPI_双打
  • 1个MPI_类型(PY+2)*(PZ+2)MPI_双打
  • 计数1、分块(PY+2)*(PZ+2)和任意步长的MPI_类型_向量,或计数PY+2、分块PZ+2和PZ+2步长的MPI_类型_向量,或任何等效组合
  • 子阵列,切片子基为[1,PY+2,PZ+2],从适当的位置开始
对于z面:

  • 计数(PX+2)*(PY+2)、blocklen 1和PZ+2步幅的MPI_类型_向量
  • 一个子阵,其切片子基为[PX+2,PY+2,1],从适当的位置开始

所以,这又一次归结为清晰性。子数组类型在所有方向上看起来最相似,区别也很明显;而如果我向你展示了一组向量类型,它们都在同一段代码中声明,你必须在白板上做一些草图,以确保我没有意外地切换它们。子数组ray也最容易推广——如果你使用一种现在每边都需要2个halo单元的方法,比如说,或者不发送角单元,那么对子数组的修改是微不足道的,而你必须做一些工作来构建向量。

Petsc的
DM
结构可能代表一个三维规则数组data那个