3

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

4

1 回答 1

7

Fortran 中二维数组的逐行分布是棘手的(但并非不可能),因为直接使用分散/收集操作,因为列主要存储。以下是两种可能的解决方案。

纯 Fortran 90 解决方案:使用 Fortran 90,您可以指定数组部分,例如A(1:4,2:3)从矩阵中取出一个小的 4x2 块A。您可以将数组切片传递给 MPI 例程。注意当前的 MPI 实现(符合现在旧的 MPI-2.2 标准),编译器将创建节数据的临时连续副本并将其传递给 MPI 例程(因为临时存储的生命周期没有明确定义,一个不应将数组部分传递给非阻塞 MPI 操作,如MPI_ISEND)。MPI-3.0 引入了新的和非常现代的 Fortran 2008 接口,它允许 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_SENDwithblktype将从指定的起始地址获取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。他的解决方案涉及将 C 矩阵拆分为列,这与此处的问题基本相同,因为 C 以行优先方式存储矩阵。Fortran 版本跟随。)

您也可以使用,MPI_SCATTERV但它非常复杂。它建立在上面介绍的纯 MPI 解决方案之上。首先,您必须将数据类型的大小调整blktype为新类型,其范围等于 of 的范围,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元素,它与子块类型的大小相匹配。

于 2012-11-29T17:37:27.483 回答