【问题标题】:process to process computation and communication in mpi在 mpi 中处理计算和通信的过程
【发布时间】:2021-05-22 04:09:25
【问题描述】:

P1 P2
P3 P4

1 2 3 4
5 6 7 8
1 2 3 4
0 6 0 8

假设 P1,P2,P3,P4 是进程,P1 有数据点 1 2 5 6 ,P2 有数据点 3 4 7 8 P3 有数据点 1 2 0 6 ,P4 有数据点 3 4 0 8。我想要对这条数据执行 stecil 计算,使得新值 6 将是 averageof(2,5,7,2) 。但是 7 是 P2 的数据点,2 是 P3 的数据点。如何解决这个问题?我可以为单个进程运行它,但是如何通过正常的 MPI_Send 和 MPI_Recv 来解决这个问题?还使用 type_contiguous 或 type_vector 和集体通信?

任何帮助将不胜感激。谢谢。

【问题讨论】:

  • 通常的解决方案是用一层或多层单元包围每个子阵列,称为“光环”或“幽灵单元”,并在每次迭代时同步它们的内容。这称为“光环交换”,使用MPI_Sendrecv 最容易实现。见here
  • 您好,您是该网站的新手,但通常会接受i.stack.imgur.com/LkiIZ.png 的回答,该回答满足 OP 的需求。因此,如果当前答案解决了您的问题,则考虑接受它

标签: c parallel-processing mpi hpc


【解决方案1】:

这个问题有一个众所周知的解决方案,名为ghost cellshalos。这个想法是根据模板用一层或多层额外的单元围绕每个子阵列。在每次迭代开始时,每个进程通过与其最近的邻居交换数据来同步光环的状态,该操作称为光环交换。光晕提供了计算所有内部单元格的新状态所需的数据,但它们缺乏自身更新所需的数据,因此这些单元格的内容在一次迭代后变得“旧”,这就是为什么它们有时被称为“幽灵” .

This blog post 清楚地表达了这个想法,但使用的语言是 Julia。由于 About 页面上说可以在其他地方重复使用内容,所以我无耻地借用了以下插图:

该图仅显示进程之间的边界上的光晕,但实际上,如果您在所有四个边上都有光晕并且只更新需要的光晕,因为它使代码更加对称并降低了其复杂性,这样会更容易。

您的特定情况下的分解如下:

1 2 3 4
5 6 7 8
1 2 3 4
0 6 0 8

先成为

1 2 | 3 4
5 6 | 7 8
----+----
1 2 | 3 4
0 6 | 0 8

添加光环:

x x x x | x x x x
x 1 2 x | x 3 4 x
x 5 6 x | x 7 8 x
x x x x | x x x x
--------+--------
x x x x | x x x x
x 1 2 x | x 3 4 x
x 0 6 x | x 0 8 x
x x x x | x x x x

现在,在模板操作之前,您需要执行光环交换。它按如下所示的步骤进行。交换沿每个维度成对执行。顺序并不重要。

1.1) 向东水平交换

每个进程将其最右边的列发送到其右侧的进程。接收器将数据放在它的左晕列中:

x x [x] x | [x] x x x     x x [x] x | [x] x x x
x 1 [2] x | [x] 3 4 x     x 1 [2] x | [2] 3 4 x
x 5 [6] x | [x] 7 8 x     x 5 [6] x | [6] 7 8 x
x x [x] x | [x] x x x     x x [x] x | [x] x x x
----------+---------- --> ----------+----------
x x [x] x | [x] x x x     x x [x] x | [x] x x x
x 1 [2] x | [x] 3 4 x     x 1 [2] x | [2] 3 4 x
x 0 [6] x | [x] 0 8 x     x 0 [6] x | [6] 0 8 x
x x [x] x | [x] x x x     x x [x] x | [x] x x x

1.2) 向西水平交换

每个进程将其最左边的列发送到它左边的进程。接收器将数据放在它的右晕列中:

x x x [x] | x [x] x x     x x x [x] | x [x] x x
x 1 2 [x] | 2 [3] 4 x     x 1 2 [3] | 2 [3] 4 x
x 5 6 [x] | 6 [7] 8 x     x 5 6 [7] | 6 [7] 8 x
x x x [x] | x [x] x x     x x x [x] | x [x] x x
----------+---------- --> ----------+----------
x x x [x] | x [x] x x     x x x [x] | x [x] x x
x 1 2 [x] | 2 [3] 4 x     x 1 2 [3] | 2 [3] 4 x
x 0 6 [x] | 6 [0] 8 x     x 0 6 [0] | 6 [0] 8 x
x x x [x] | x [x] x x     x x x [x] | x [x] x x

2.1) 向南垂直交换

每个进程将其底行发送给它下面的进程。接收器将数据放置在其顶部的光环行中:

[x x x x | x x x x]   [x x x x | x x x x]
 x 1 2 3 | 2 3 4 x     x 1 2 3 | 2 3 4 x
[x 5 6 7 | 6 7 8 x]   [x 5 6 7 | 6 7 8 x]
 x x x x | x x x x     x x x x | x x x x
 --------+-------- --> --------+--------
[x x x x | x x x x]   [x 5 6 7 | 6 7 8 x]
 x 1 2 3 | 2 3 4 x     x 1 2 3 | 2 3 4 x
[x 0 6 0 | 6 0 8 x]   [x 0 6 0 | 6 0 8 x]
 x x x x | x x x x     x x x x | x x x x

2.2) 向北垂直交换

每个进程将其顶行发送给它上面的进程。接收器将数据放置在其底部的光环行中:

 x x x x | x x x x     x x x x | x x x x
[x 1 2 3 | 2 3 4 x]   [x 1 2 3 | 2 3 4 x]
 x 5 6 7 | 6 7 8 x     x 5 6 7 | 6 7 8 x
[x x x x | x x x x]   [x 1 2 3 | 2 3 4 x]
 --------+-------- --> --------+--------
 x 5 6 7 | 6 7 8 x     x 5 6 7 | 6 7 8 x
[x 1 2 3 | 2 3 4 x]   [x 1 2 3 | 2 3 4 x]
 x 0 6 0 | 6 0 8 x     x 0 6 0 | 6 0 8 x
[x x x x | x x x x]   [x x x x | x x x x]

这些操作中的每一个都可以通过一个 MPI 调用来实现 - MPI_Sendrecv。发送部分发送本地数据行或列,接收部分将其接收到本地晕行或列中。

四次交换后的最终结果是:

x x x x | x x x x
x 1 2 3 | 2 3 4 x
x 5 6 7 | 6 7 8 x
x 1 2 3 | 2 3 4 x
--------+--------
x 5 6 7 | 6 7 8 x
x 1 2 3 | 2 3 4 x
x 0 6 0 | 6 0 8 x
x x x x | x x x x

您可能会注意到,即使是每个光环中的角元素也包含人们期望的正确对角元素。这样做的美妙之处在于,您可以简单地添加更多步骤,将其扩展到所需的任意维度,并且所有元素都会自动在光环区域中找到它们的正确位置。

您现在可以在本地获得所有 6 个 neigbhours,并且可以继续对它们的值求平均值。请注意,您只使用光环中的值,不要更新它们。

一些补充说明:

  1. 无论您是否有周期性边界条件,这都有效。在周期性边界条件下,当进行右移时,P2 是 P1 的右邻居,P1 是 P2 的右邻居。此外,P1 是 P2 的左邻居,P2 是 P1 的左邻居。垂直方向也是如此。

  2. 您不需要特殊的代码来处理边界上的进程。如果一个进程没有右(或左、上或下)邻居,只需向/从MPI_PROC_NULL 发送或接收消息。即,代码如下:

    int right = compute_right_rank();
    int left = compute_left_rank();
    int up = compute_top_rank();
    int down = compute_bottom_rank();
    
    MPI_Sendrecv(right_column, 1, columndt, right, 0,
                 left_halo, 1, columndt, left, 0,
                 MPI_COMM_WORLD, MPI_STATUS_IGNORE);
    
    MPI_Sendrecv(left_column, 1, columndt, left, 0,
                 right_halo, 1, columndt, right, 0,
                 MPI_COMM_WORLD, MPI_STATUS_IGNORE);
    
    MPI_Sendrecv(bottom_row, 1, rowdt, down, 0,
                 top_halo, 1, rowdt, up, 0,
                 MPI_COMM_WORLD, MPI_STATUS_IGNORE);
    
    MPI_Sendrecv(top_column, 1, rowdt, up, 0,
                 bottom_halo, 1, rowdt, down, 0,
                 MPI_COMM_WORLD, MPI_STATUS_IGNORE);
    

    这里,compute_right_rank() 应该返回 rank + 1 如果右边有排名,否则返回 MPI_PROC_NULL。发送到MPI_PROC_NULL 或从它接收是无操作的,即没有任何反应。它允许您在没有ifs 的情况下编写代码。

    columndt 是一种 MPI 数据类型,对应于数组中的一列。您可以使用MPI_Type_vector 构建它。 rowdt 是一种 MPI 数据类型,表示数组中的一整行。使用MPI_Type_contiguous 构造它。

  3. 如果排名在笛卡尔通信器中,计算邻居的排名非常容易。 MPI_Cart_createMPI_Cart_shift 是你最好的朋友。您还可以使用 MPI 邻居集合来进一步减少 MPI 调用的数量。

  4. 由于边界条件不是周期性的,因此未填充底部等级的底部光晕。最右边行列的右光晕、最高行列的顶部光晕和最左边行列的左光晕也是如此。您可能希望使用特殊值预填充它们,例如 0。这个价值永远不会改变,因为没有沟通可以把东西放在那里。

  5. 如果您的计算是迭代的,您必须在每次迭代之前执行光环。如果光环交换与计算相比太慢,您可以增加光环的厚度并使其厚两层或多层。使用外层更新内层中的值。三层厚的晕轮需要每三次迭代交换一次。

【讨论】:

    猜你喜欢
    • 2012-12-07
    • 2010-11-11
    • 1970-01-01
    • 1970-01-01
    • 2017-05-21
    • 2015-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多