mpi - 分解矩阵,以便每个进程使用 MPI 获得矩阵的份额

标签 mpi multidimensional-array

我对使用 MPI 还很陌生。我的问题如下:我有一个矩阵,其中 2000 行和 3 列存储为二维数组(不是连续数据)。在不改变数组结构的情况下,根据进程数np,每个进程应该得到矩阵的一部分。
例子:
A: 2000 个 3 列的二维数组,np = 2,然后 P0 得到 A 的前半部分,即前 1000 行乘 3 列的二维数组,P1 得到后半部分,即后 1000 行3 列。
现在 np 可以是任何数字(只要它除以行数)。有什么简单的方法可以解决这个问题吗?
我将不得不使用 FORTRAN 90 来完成这项任务。
谢谢

最佳答案

由于列优先存储,直接使用分散/聚集操作在 Fortran 中按行分布 2D 数组很棘手(但并非不可能)。以下是两种可能的解决方案。

纯 Fortran 90 解决方案:使用 Fortran 90,您可以指定数组部分,例如 A(1:4,2:3) ,这将从矩阵 A 中取出一个小的 4x2 块。您可以将数组切片传递给 MPI 例程。注意当前的 MPI 实现(符合现在旧的 MPI-2.2 标准),编译器将创建段数据的临时连续副本并将其传递给 MPI 例程(因为临时存储的生命周期没有明确定义,一个不应将数组部分传递给非阻塞 MPI 操作,如 MPI_ISEND )。 MPI-3.0 引入了新的非常现代的 Fortran 2008 接口(interface),允许 MPI 例程直接获取数组部分(没有中间数组),并支持将部分传递给非阻塞调用。

对于数组部分,您只需在根进程中实现一个简单的 DO 循环:

INTEGER :: i, rows_per_proc

rows_per_proc = 2000/nproc
IF (rank == root) THEN
  DO i = 0, nproc-1
    IF (i /= root) THEN
      start_row = 1 + i*rows_per_proc
      end_row = (i+1)*rows_per_proc
      CALL MPI_SEND(mat(start_row:end_row,:), 3*rows_per_proc, MPI_REAL, &
                    i, 0, MPI_COMM_WORLD, ierr)
    END IF
  END DO
ELSE
  CALL MPI_RECV(submat(1,1), 3*rows_per_proc, MPI_REAL, ...)
END IF

纯 MPI 解决方案(也适用于 FORTRAN 77):首先,您必须使用 MPI_TYPE_VECTOR 声明向量数据类型。块的数量是 3 ,块长度是每个进程应该得到的行数(例如 1000 ),步幅应该等于矩阵的总高度(例如 2000 )。如果此数据类型称为 blktype ,则以下内容将发送矩阵的上半部分:
REAL, DIMENSION(2000,3) :: mat

CALL MPI_SEND(mat(1,1), 1, blktype, p0, ...)
CALL MPI_SEND(mat(1001,1), 1, blktype, p1, ...)

使用 MPI_SEND 调用 blktype 将从指定的起始地址获取 1000 元素,然后跳过下一个 2000 - 1000 = 1000 元素,获取另一个 1000 等等,总共 3 次。这将形成大矩阵的 1000 行子矩阵。

您现在可以运行一个循环来向通信器中的每个进程发送不同的子块,从而有效地执行分散操作。为了接收这个子块,接收过程可以简单地指定:
REAL, DIMENSION(1000,3) :: submat

CALL MPI_RECV(submat(1,1), 3*1000, MPI_REAL, root, ...)

如果您不熟悉 MPI,这就是您需要了解的有关 Fortran 中按行散布矩阵的全部信息。如果您很了解 MPI 的类型系统是如何工作的,那么请提前阅读更优雅的解决方案。

(有关如何使用 Jonathan Dursi 的 MPI_SCATTERV 执行此操作的出色描述,请参阅 here。他的解决方案处理按列拆分 C 矩阵,这与此处的问题基本相同,因为 C 以行主要方式存储矩阵。Fortran版本如下。)

您也可以使用 MPI_SCATTERV 但它非常复杂。它建立在上面介绍的纯 MPI 解决方案之上。首先,您必须将 blktype 数据类型的大小调整为一个新类型,该类型的范围等于 MPI_REAL 的范围,以便可以指定数组元素中的偏移量。这是必需的,因为 MPI_SCATTERV 中的偏移量是指定数据类型范围的倍数,而 blktype 的范围是矩阵本身的大小。但是由于跨步存储,两个子块的起始位置仅相隔 4000 字节( 1000 乘以 MPI_REAL 的典型范围)。要修改类型的范围,可以使用 MPI_TYPE_CREATE_RESIZED :
INTEGER(KIND=MPI_ADDRESS_KIND) :: lb, extent

! Get the extent of MPI_REAL
CALL MPI_TYPE_GET_EXTENT(MPI_REAL, lb, extent, ierr)
! Bestow the same extent upon the brother of blktype
CALL MPI_TYPE_CREATE_RESIZED(blktype, lb, extent, blk1b, ierr)

这将创建一个新的数据类型 blk1b ,它具有 blktype 的所有特征,例如可用于发送整个子块,但在数组操作中使用时,MPI 只会以单个 MPI_REAL 的大小而不是整个矩阵的大小来推进数据指针。使用这种新类型,您现在可以将 MPI_SCATTERV 的每个块的开头定位在 mat 的任何元素上,包括任何矩阵行的开头。具有两个子块的示例:
INTEGER, DIMENSION(2) :: sendcounts, displs

! First sub-block
sendcounts(1) = 1
displs(1) = 0
! Second sub-block
sendcounts(2) = 1
displs(2) = 1000

CALL MPI_SCATTERV(mat(1,1), sendcounts, displs, blk1b, &
                  submat(1,1), 3*1000, MPI_REAL, &
                  root, MPI_COMM_WORLD, ierr)

这里第一个子块的位移是 0 ,与矩阵的开头重合。第二个子块的位移是 1000 ,即它将从第一列的第 1000 行开始。在接收方的数据计数参数是 3*1000 元素,它匹配子块类型的大小。

关于mpi - 分解矩阵,以便每个进程使用 MPI 获得矩阵的份额,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13630529/

相关文章:

c - 分配第二个数组时出现段错误

c - 在sqlite的回调函数中动态重新分配2个dim数组

c++ - 如何检查矩阵的任何行是否包含所有 'A' ?

c++ - 在内部, vector 的 vector 如何扩展? C++

c - 访问三维字符数组

sockets - MPI消息可用性通知

c++ - 16 位 float MPI_Reduce?

php - 将多维数组的值转换为一维数组中的键值对,同时从其中一个值中取出前缀?

c - Eclipse 和 Yosemite 中的全局环境变量

c - 中止 MPI 中所有进程的执行