理解2D MPI网格中的维度、坐标和进程排序

11

我有3个问题,都与MPI(用C语言编写)相关。 我认为前两个问题的答案相同,但我不能确定。


问题1

使用MPI_Dims_create创建2D网格时,返回的维度中哪个是X,哪个是Y?

例如:

int numProcs = -1, myProcID = -1;
MPI_Comm_rank(MPI_COMM_WORLD, &myProcID);
MPI_Comm_size(MPI_COMM_WORLD, &numProcs);

int size[2];
size[0] = size[1] = 0;
MPI_Dims_create(numProcs, 2, size);
int numProcs_y = -1, numProcs_x = 1;

它应该是:

numProcs_y = size[0];
numProcs_x = size[1];

或者这个:
numProcs_x = size[0];
numProcs_y = size[1];

我之前尝试使用一些进程数来运行程序(例如6),使用6个进程时,MPI_Dims_create应该会创建一个3行2列或者2行3列的网格结构(除非我误解了文档)。对于第三个进程(共有6个),我发现size[0]=1size[1]=0(我相信这对应于x=0,y=1)。这似乎表明MPI_Dims_create正在创建一个3行2列的网格结构(如果是2行3列的话,第二个进程应该会有x=2,y=0)。欢迎任何人提供确认。


问题2

在使用2D笛卡尔网格结构的MPI_Cart_coords函数时,返回值中哪一个维度代表X轴,哪一个代表Y轴?

例如:

int periodic[2];
periodic[0] = periodic[1] = 0; // no wrap around
MPI_Comm cart_comm;
int coords[2];

// using size from question 1
MPI_Cart_create(MPI_COMM_WORLD, 2, size, periodic, 1, &cart_comm);
MPI_Cart_coords(cart_comm, myProcID, 2, coords);

和问题1类似,我的问题是,应该是这样的吗?

myProcID_y = coords[0];
myProcID_x = coords[1];

或者像这样

myProcID_x = coords[0];
myProcID_y = coords[1];

我已经查阅了文档和之前在这里提出的问题,但似乎没有直接回答这个问题。

文档似乎表明第一种方法是正确的,但它并没有明确说明。


问题三

前两个问题背后的问题是,我正在尝试将二维网格分割成行和列。但是,当我使用 MPI_Comm_rank 检查每个进程的行和列 ID 时,我得到的进程顺序与我认为上述问题的答案不匹配。

根据上述内容,我期望进程的顺序如下:

P0 P1
P2 P3
P4 P5

然而,使用以下代码(在上述代码之后出现在我的程序中,因此可以从中访问所有上述代码:我正在尝试将我的代码分离以使问题更容易隔离):
MPI_Comm row_comm, col_comm;
int row_id = -1, col_id = -1;
// ** NOTE: My use of myProcID_y and myProcID_x here are based 
// on my understanding of the previous 2 questions ... if my 
// understanding to one/both of those is wrong, then obviously the assignments 
// here are wrong too.
// create row and column communicators based on my location in grid
MPI_Comm_split(cart_comm, myProcID_y, myProcID_x, &row_comm);
MPI_Comm_split(cart_comm, myProcID_x, myProcID_y, &col_comm);

// get row and column ID for each process
MPI_Comm_rank(row_comm, &row_id);
MPI_Comm_rank(col_comm, &col_id);
printf("Process: %d\trowID: %d\tcolID: %d\n", myProcID, row_id, col_id);

我看到了以下内容被打印出来:

Process: 0 rowID: 0        colID: 0
Process: 1 rowID: 1        colID: 0
Process: 2 rowID: 0        colID: 1
Process: 3 rowID: 1        colID: 1
Process: 4 rowID: 0        colID: 2
Process: 5 rowID: 1        colID: 2

这似乎对应以下进程的顺序:

P0 P2 P4
P1 P3 P5

这与我从MPI_Dims_create期望的矩阵尺寸相反(2行3列)。

假设我对前两个问题的理解是正确的(即Y是这些函数返回的第一个维度),那么为什么在这一步中进程(表面上)被以不同的顺序排序了呢?

1个回答

6
Q1和Q2。在MPI中,没有维度X和维度Y这样的东西 - 这些是您为标准中使用的抽象维度提供的标签。 MPI使用编号的维度,并遵循C行主要编号的排名,例如,在2x3笛卡尔拓扑结构中,(0,0)映射到排名0(0,1)映射到排名1(0,2)映射到排名2(1,0)映射到排名3,以此类推。
请注意,维度0对应于坐标元组中最右边的元素。这与C数组中元素编号的顺序相反,通常会引起混淆。要创建2x3笛卡尔拓扑结构,必须将size数组初始化为:
int size[2] = { 3, 2 };

您需要将抽象的编号维度映射到您的问题上。您可以选择将维度0作为X,或者选择将维度1作为X - 这并不重要。

至于MPI_DIMS_CREATE,标准规定如下:

使用适当的可除性算法,将维度设置为尽可能接近。

对于调用设置的dims[i]dims[i]将按非递增顺序排序。

此操作只返回具有以下属性的元素数组dims[i](除非在调用MPI_DIMS_CREATE之前通过在dims[]中设置一个非零值来固定一个或多个维度的大小):

  • dims[0] >= dims[1] >= dims[2] >= ...
  • dims[0] * dims[1] * dims[2] == nprocs,其中nprocs是指定给MPI_DIMS_CREATE的进程数。

这意味着,当MPI_DIMS_CREATEnprocs集合分解为多维网格时,它会将最大乘数分配给维度0的大小,将下一个乘数分配给维度1的大小,以此类推。由于6个因子是2*3,因此MPI_DIMS_CREATE将返回{3, 2}。如果您直接使用MPI_DIMS_CREATE的结果调用MPI_CART_CREATE,它将创建一个带有坐标和排名的2x3拓扑结构:

(0,0)=0 (0,1)=1 (0,2)=2
(1,0)=3 (1,1)=4 (1,2)=5

Q3. MPI提供了一个特殊的例程来分割笛卡尔拓扑结构-MPI_CART_SUB。它接受一个名为 remain_dims 的逻辑标志数组(在C中是整数)。每个非零的 remain_dims[i] 表示应在生成的分割中保留该维度i,而任何可能的非保留维度组合都将创建单独的子通信器。例如,给定 2x3 拓扑结构:

  • remain_dims[] = { 1, 0 } 会保留维度 0 并导致 2 个不重叠的具有 3 个进程的一维通信器;
  • remain_dims[] = { 0, 1 } 会保留维度 1 并导致 3 个不重叠的具有 2 个进程的一维通信器;
  • remain_dims[] = { 0, 0 } 不会保留这两个维度之一,并且将导致 6 个不重叠的零维通信器,每个通信器中有一个进程。

你如何称呼这种分割方式是按行还是按列进行分割,取决于你对笛卡尔坐标系的标记。


需要注意的一点是,人们经常在由网络SMP或NUMA多核节点组成的系统上运行MPI代码。此时,适当的二维网格将是 nodes x cores。如果已知核心数,则可以在调用 MPI_DIMS_CREATE 时轻松地固定它:

int size[2] = { ncores, 0 };

MPI_Dims_create(numProcs, 2, size);

这比将numProcs除以ncores并检查可被整除性更为方便,因为如果ncores无法整除numProcsMPI_DIMS_CREATE将会发出错误信号。然后可以进行保持维度0的分区,即:
int remain_dims[2] = { 1, 0 };

会创建包含同一节点上进程的子通讯器,而不会。
int remain_dims[2] = { 0, 1 };

将会创建包含来自同一节点的任意两个进程的子通信器。


请注意,在您的代码中,您已经在MPI_CART_CREATEreorder参数中指定了1(true)的值。这可能导致在笛卡尔通信器中,进程在MPI_COMM_WORLD和笛卡尔通信器中具有不同的排名。因此,以下代码行不保证会按您的期望执行:

MPI_Cart_coords(cart_comm, myProcID, 2, coords);
                           ^^^^^^^^
...
printf("Process: %d\trowID: %d\tcolID: %d\n", myProcID, row_id, col_id);
                                              ^^^^^^^^

myProcID 是从 MPI_COMM_WORLD 中获取的,可能与 cart_comm 中的新秩不同,因此不应使用它来获取进程坐标和执行拆分操作。


了解X和Y维度很重要。 一个问题:你的2x3个进程ID网格假设你将X分配给第0维,对吧(假设你将行数分配给第0维,则行主序排列似乎为3行x2列)?我将重新排序参数更改为false,以防止您在最后提到的情况,但我确实获得了相同的进程顺序,即在第0行中具有P0、P2和P4的顺序。 您是说MPI_Comm_split()不应该用于笛卡尔网格,而应该改用MPI_Cart_sub()吗? - Matt Sinclair
你可能可以忽略第一个问题。我忘记了你关于第0维映射到元组中最右边的元素的说法。不过,我仍然对MPI_Cart_sub()的问题很感兴趣。 - Matt Sinclair
1
@MattSinclair,我并不是说你不能或者不应该使用MPI_COMM_SPLIT在笛卡尔网格上进行分区。我只是想说,特别是对于高维网格,MPI_CART_SUB更加方便和多才多艺。使用颜色/关键字分割,您需要计算颜色,而每增加一个维度,这个过程就会变得越来越繁琐。此外,MPI_CART_SUB创建笛卡尔子网格,而MPI_COMM_SPLIT创建没有相关拓扑结构的简单通信器。 - Hristo Iliev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接