我是使用MPI的新手。我的问题如下:我有一个2000行和3列的矩阵存储为2D数组(不是连续的数据)。在不改变数组结构的情况下,根据进程数np,每个进程都应该得到矩阵的一部分。 例: A:由2列组成的2000个数组的二维数组,np = 2,然后P0得到A的前半部分,它是前1000行乘3列的2D数组,P1得到后半部分,即后续的1000行3列。 现在np可以是任何数字(只要它划分行数)。有什么简单的方法吗? 我将不得不使用FORTRAN 90进行此分配。 谢谢
答案 0 :(得分:7)
由于列主要存储,使用分散/收集操作直接在Fortran中按行分布2D阵列是棘手的(但并非不可能)。接下来是两种可能的解决方案。
Pure Fortran 90解决方案:使用Fortran 90,您可以指定像A(1:4,2:3)
这样的数组部分,它会从矩阵A
中取出一个小的4x2块。您可以将数组切片传递给MPI例程。注意当前的MPI实现(符合现在的旧MPI-2.2标准),编译器将创建段数据的临时连续副本并将其传递给MPI例程(因为临时存储的生命周期没有很好地定义,一个不应将数组sectons传递给非阻塞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
),步幅应等于矩阵的总高度(例如{ {1}})。如果此数据类型名为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
次。这将形成你的大矩阵的1000行子矩阵。
现在,您可以运行循环,为通信器中的每个进程发送不同的子块,从而有效地执行分散操作。为了接收该子块,接收过程可以简单地指定:
3
如果您不熟悉MPI,那么您需要了解Fortran中按行散布矩阵的所有信息。如果你很清楚MPI的类型系统是如何工作的,那么请提前阅读以获得更优雅的解决方案。
(请参阅here以获得Jonathan Dursi对REAL, DIMENSION(1000,3) :: submat
CALL MPI_RECV(submat(1,1), 3*1000, MPI_REAL, root, ...)
如何做到这一点的精彩描述。他的解决方案涉及在列中拆分C矩阵,这基本上与此处的问题相同因为C以行主要方式存储矩阵.Fortran版本如下。)
您也可以使用MPI_SCATTERV
,但它非常复杂。它建立在上面提出的纯MPI解决方案之上。首先,您必须将MPI_SCATTERV
数据类型的大小调整为一个新类型,其范围等于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
的所有特征,例如可以用来发送整个子块,但是当在数组操作中使用时,MPI只会使用单个blktype
的大小而不是整个矩阵的大小来推进数据指针。使用这种新类型,您现在可以将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
个元素,它们匹配子块类型的大小。