【问题标题】:Number of subarray data types for exchanging 2D halos in 3D process decomposition in MPI用于在 MPI 中的 3D 过程分解中交换 2D 晕圈的子阵列数据类型的数量
【发布时间】:2015-02-02 00:09:48
【问题描述】:

假设在每个进程上使用 3D 笛卡尔拓扑将维度为 GX*GY*GZ 的全局多维数据集分解为大小为 PX*PY*PZ3D 多维数据集。添加 Halo 以交换数据,这将变为 (PX+2)*(PY+2)*(PZ+2)假设我们使用子数组数据类型进行2D 光环交换 - 我们需要定义12 子数组类型吗?

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

【问题讨论】:

  • Petsc 的DM 结构可能表示分布在某些处理器上的三维规则数组数据:它可能是您正在寻找的。看看DMDACreate3d()petsc的documentation的第2.4节也很有趣:在petsc中光环被称为鬼点。
  • 感谢您回复@francis,但这不是我要查看的内容。我正在考虑交换 3D 数据的 XY、YZ、ZX 平面。 Petsc 及其文档对于我正在制作的这个简单的 MPI 程序来说太复杂了。
  • 当我为 YZ 平面定义它时,MPI_Type_create_subarray 工作正常(X 是常数)。详细说明:X 轴是上/下,Y 轴是左/右,Z 轴是朝向你/远离你(即 MPI_ORDER_C)。它不适用于其他飞机。这就是问题所在。

标签: c 3d parallel-processing 2d mpi


【解决方案1】:

您在这里有三种数据访问模式 - 发送/接收子域的 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+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_Type_vector,或
  • 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->

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

  • 计数 PX+2、blocklen (PZ+2) 和 (PY+2)*(PZ+2) 的步幅的 MPI_Type_vector,或
  • 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, ... );

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

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 面(这里您有更多选择,因为它们是连续的:

  • (PY+2)*(PZ+2) MPI_Doubles
  • 1 MPI_Type_Contiguous of (PY+2)*(PZ+2) MPI_Doubles
  • MPI_Type_vector 计数为 1、blocklen (PY+2)*(PZ+2) 和任何东西的步幅,或计数 PY+2、blocklen PZ+2 和 PZ+2 的步幅,或任何等效组合
  • 一个子数组,切片子大小为 [1,PY+2,PZ+2],从适当的位置开始

对于 z 面:

  • 计数 (PX+2)*(PY+2)、blocklen 1 和 PZ+2 步幅的 MPI_Type_vector
  • 一个子数组,切片子大小为 [PX+2,PY+2,1],从适当的位置开始。

所以,这一切都归结为清晰。子数组类型在各个方向之间看起来最相似,并且差异相当明显;然而,如果我向您展示了一组在同一段代码中声明的向量类型,您将不得不在白板上画一些草图,以确保我没有意外地切换它们。子数组也最容易泛化 - 如果您移动到现在每侧需要 2 个光环单元的方法,例如,或者不发送角单元,则对子数组的修改是微不足道的,而您必须做一些工作用向量构建一些东西。

【讨论】:

  • 感谢您煞费苦心地解释它。但是我迷失在句子你需要向上邻居发送y-face的数据看起来像(见第一个图之前的句子)。请您确认图中的起始坐标是否正确?你画过这个从上往下看顶面的图形吗?在我理解整个答案之前,我真的需要理解这句话。非常感谢!
  • @GauravSaxena - 他们是正确的,但我重新做了这些数字,希望能更清楚。希望对您有所帮助。
  • 再次感谢。我从答案中带回家了两件事。 (1) 可以使用子阵列/向量 (2) 在这种情况下,子阵列更加对称。我可以成功地通过子数组传递二维数据。我也将尝试使用向量——更多地锻炼我的大脑!非常感谢您的回答:)。
  • 我一直在寻找答案。很好的答案,对我帮助很大!
猜你喜欢
  • 2013-07-08
  • 2012-04-01
  • 2018-12-20
  • 1970-01-01
  • 2012-03-16
  • 2020-11-09
  • 2021-12-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多