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

Gau*_*ena 4 c 3d parallel-processing 2d mpi

假设一个全局的维度立方体,GX*GY*GZ使用3D笛卡尔拓扑将其分解为每个过程中3D的大小多维数据集PX*PY*PZ.添加Halos以便交换数据(PX+2)*(PY+2)*(PZ+2).假设我们使用Subarray数据类型进行2D光环交换 - 我们是否需要定义12子数组类型?

我的理由是:对于YZ平面,我们创建一个Subarray类型用于发送,一个子阵列类型用于接收,因为起始坐标将在子阵列数据类型本身中指定.但是有2 YZ平面,导致4Subarray数据类型.虽然全局和本地数据大小保持不变但由于起始索引 - 我们需要定义4不同的子阵列类型.使用Vector数据类型发送其中四个平面是不是更好,其余两个使用Subarray数据类型?

Jon*_*rsi 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->
Run Code Online (Sandbox Code Playgroud)

也就是说,它将从[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的MPI_Type_vector,blocklen(PZ + 2)和(PY + 2)*(PZ + 2)的步幅,或者
  • MPI_Type_subarray,切片子范围为[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->
Run Code Online (Sandbox Code Playgroud)

至关重要的是,所需的数据模式完全相同:PZ + 2值,然后从最后一个块的开头跳过(PY + 2)*(PZ + 2)值,以及另一个PZ + 2值.我们可以将其描述为:

  • 计数PX + 2的MPI_Type_vector,blocklen(PZ + 2)和(PY + 2)*(PZ + 2)的步幅,或者
  • MPI_Type_subarray,切片子范围为[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, ... );
Run Code Online (Sandbox Code Playgroud)

它声明了四个具有不同起点的等效模式,您也可以只定义一个,并使用它指向您需要的数据的不同起点:

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, ... );
Run Code Online (Sandbox Code Playgroud)

以上就是您使用相应矢量类型的方式 - 将其指向要发送/接收的第一个项目.

如果您选择使用子阵列类型,那么使用它的任何一种方式都非常好,您将看到在各种软件中做出的两种选择.这只是你更清楚的问题 - 每种模式有4种类型(取决于偏移),或者在发送/接收中明确使用偏移.我个人认为1类方法更加清晰,但对于这个问题没有明确的正确答案.

至于是否使用MPI_Subarray或Vector(比如说),最简单的方法是看看你需要支持的其他两种模式:使用X面(这里有更多选项,因为它们是连续的:

  • (PY + 2)*(PZ + 2)MPI_Doubles
  • 1 MPI_Type_Contiguous(PY + 2)*(PZ + 2)MPI_Doubles
  • 计数1的MPI_Type_vector,blocklen(PY + 2)*(PZ + 2),任何步幅,或计数PY + 2,blocklen PZ + 2,PZ + 2的步幅,或任何等效组合
  • 从适当的位置开始,具有[1,PY + 2,PZ + 2]的切片子集的子阵列

对于z-faces:

  • 计数的MPI_Type_vector(PX + 2)*(PY + 2),blocklen 1和PZ + 2的步幅
  • 从适当的位置开始,具有[PX + 2,PY + 2,1]的切片子集的子阵列.

所以,这一切都归结为清晰.子阵列类型在所有方向之间看起来最相似,差异相当明显; 而如果我向你展示了一堆所有在同一段代码中声明的矢量类型,你必须在白板上做一些素描,以确保我没有意外地切换它们.子阵列也最容易推广 - 如果你转向一个方法,现在每边需要2个晕圈细胞,或者不发送角落细胞,对子阵列的修改是微不足道的,而你必须做一些工作用向量构建东西.