私はMPIの使用にかなり慣れていません。私の質問は次のとおりです。2000行と3列が2D配列(連続データではない)として格納されている行列があります。配列の構造を変更せずに、プロセスの数npに応じて、各プロセスはマトリックスの一部を取得する必要があります。例:A:2000配列×3列の2D配列、np = 2の場合、P0はAの前半を取得します。これは最初の1000行×3列の2D配列になり、P1は後半を取得します。これは次の1000になります。 3列の行。これで、npは任意の数にすることができます(行数を分割する限り)。これを行う簡単な方法はありますか?この割り当てにはFORTRAN90を使用する必要があります。ありがとうございました
1 に答える
Fortranでの2D配列の行方向の分散は、列が主要なストレージであるため、スキャッター/ギャザー操作を直接使用するのは難しいです(ただし不可能ではありません)。2つの可能な解決策が続きます。
純粋なFortran90ソリューション:Fortran 90を使用するA(1:4,2:3)
と、マトリックスから小さな4x2ブロックを取り出すような配列セクションを指定できますA
。配列スライスをMPIルーチンに渡すことができます。現在のMPI実装(現在は古いMPI-2.2標準に準拠)では、コンパイラはセクションデータの一時的な連続コピーを作成し、それをMPIルーチンに渡します(一時ストレージの存続期間が明確に定義されていないため、1つ)のような非ブロッキングMPI操作に配列セクションを渡してはなりませんMPI_ISEND
。MPI-3.0は、MPIルーチンが(中間配列なしで)配列セクションを直接取得できるようにし、非ブロッキング呼び出しへのセクションの受け渡しをサポートする、新しく非常に最新のFortran2008インターフェースを導入しています。
配列セクションを使用する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
、ブロックの長さは各プロセスが取得する必要のある行の数であり(eg 1000
)、ストライドはマトリックスの全高に等しくなければなりません(eg 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
これで、ループを実行して、コミュニケーターの各プロセスに異なるサブブロックを送信し、スキャッター操作を効果的に実行できます。このサブブロックを受信するために、受信プロセスは単純に以下を指定できます。
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
、範囲が同じである新しい型に変更する必要があります。のオフセットは指定されたデータ型の範囲の倍数で指定され、の範囲は行列自体のサイズである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
2つのサブブロックの例:
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
、これはマトリックスの開始と一致します。2番目のサブブロックの変位はです1000
。つまり、最初の列の1000行目から始まります。受信者側では、データカウント引数は3*1000
要素であり、サブブロックタイプのサイズと一致します。