6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

fortran+MPIコーディング入門(3) 1対1通信

Last updated at Posted at 2018-11-03

前回は多対多の集団通信について学びました。ですが、実際「となりのプロセスにだけデータを送りたい」というような場合もよくあります。今回はそういう場合に使う1対1通信について学びます。

目次(予定)

  1. 並列処理とは
  2. [プロセス並列の基本] (https://qiita.com/items/5cce1174ac48367948bc)
  3. 集団通信
  4. 1対1通信 ←今ココ
  5. オブジェクト指向コードの並列化

1対1通信

MPI_sendとMPI_recv

文字通りデータを送ります。

ブロッキング通信なので、通信が正常に終了しなければ終了しません。

サンプル

このサンプルでは、右隣に自身のランクを送信し、左隣からランクを受信します。

subroutine test_01()
    integer :: n_data
    integer :: dest     ! 送り先
    integer :: origin   ! 受信データの送信元
    
    integer :: buf                  ! 受け取るデータ
    integer,allocatable :: ista(:)  ! ステータス格納用

    allocate(ista(mpi_status_size))
    
    write(LOGUNIT,*) "test 01"

    buf = me
    n_data = 1
    
    if(me == nnn-1) then
       dest = 0
    else
       dest = me+1
    end if

    if(me == 0) then
       origin = nnn-1
    else
       origin = me-1
    end if

    write(LOGUNIT,*) "origin,dest=", origin,dest
    
    call mpi_send(buf, n_data, mpi_integer , dest, &
         me, mpi_comm_world, ierr)
    call mpi_recv(buf, n_data, mpi_integer, origin, &
         origin, mpi_comm_world,ista,ierr)

    write(LOGUNIT,*) "buf:", buf
        
  end subroutine test_01

MPI_sendrecv

sendとrecvは基本的にセットで呼ばれるので、両方まとめたものがこれです。

subroutine test_02()
    integer :: n_data
    integer :: dest
    integer :: origin
    
    integer :: buf
    integer,allocatable :: ista(:) 

    allocate(ista(mpi_status_size))
    
    write(LOGUNIT,*) "test 02"

    buf = me
    n_data = 1
    
    if(me == nnn-1) then
       dest = 0
    else
       dest = me+1
    end if

    if(me == 0) then
       origin = nnn-1
    else
       origin = me-1
    end if

    write(LOGUNIT,*) "origin,dest=", origin,dest
    
    call mpi_sendrecv(me, n_data, mpi_integer , dest, me,&
         buf, n_data,mpi_integer, origin, origin, & 
         mpi_comm_world, ista, ierr)

    write(LOGUNIT,*) "buf:", buf
        
  end subroutine test_02

MPI_IsendとMPI_Irecv

mpi_sendとmpi_recvはブロッキング通信ですが、ブロッキングでないものがmpi_isendとmpi_irecvです。

送受信を完了するにはMPI_waitを使う必要があります。

subroutine test_03()
    integer :: n_data
    integer :: dest
    integer :: origin
    
    integer :: buf
    integer :: ireq
    integer,allocatable :: ista(:) 

    allocate(ista(mpi_status_size))
    
    write(LOGUNIT,*) "test 03"

    buf = me
    n_data = 1
    
    if(me == nnn-1) then
       dest = 0
    else
       dest = me+1
    end if

    if(me == 0) then
       origin = nnn-1
    else
       origin = me-1
    end if

    write(LOGUNIT,*) "origin,dest=", origin,dest
    
    call mpi_isend(me, n_data, mpi_integer , dest, &
         me, mpi_comm_world, ireq, ierr)
    call mpi_irecv(buf, n_data, mpi_integer, origin, &
         origin, mpi_comm_world,ireq,ierr)

    call mpi_wait(ireq, ista, ierr)
    
    write(LOGUNIT,*) "buf:", buf
        
  end subroutine test_03

#デッドロック
集団通信と異なり、1対1通信では厄介な問題が生じることがあります。それはデッドロックです。

一般にデッドロックとは、複数の処理同士が互いの処理の終了を待っている状態になり、処理が完全に停止してしまうことを言います。排他制御などをかじったことがある人なら知っているかも知れません。

MPIでは次のようにしてデッドロックが生じます。以下のコードはデッドロックを起こし得ます。

! n_dataはかなり大きい
call mpi_recv(buf_recv, n_data, mpi_double_precision, dest, tag, mpi_comm_world, ista,ierr)
call mpi_send(buf_send, n_data, mpi_double_precision, dest, tag, mpi_comm_world, ,ierr)

このコードを実行すると、両方がデータの受信待ちになるものの、データをsendしてくれる相手がいないので、デッドロックになります。特にデータサイズが大きいときにおこりやすいです。

逆にしても同じです。

call mpi_send(buf_send, n_data, mpi_double_precision, dest, tag, mpi_comm_world, ierr)
call mpi_recv(buf_recv, n_data, mpi_double_precision, dest, tag, mpi_comm_world, ista,ierr)

mpi_sendは受信する相手がいないと正常に終了しません。なので順番を変えてもデッドロックになります。

厄介なことに、デッドロックは場合によっては起こったり起こらなかったりします。ログが出ることもありません。その上abortするわけでもありません1 。そのため「MPIプログラムが沈黙したらデッドロックを疑う」と知っていなければいけません。

解決策

1.MPI_sendrecvを使う

MPI_sendrecvを使えばデッドロックを防げます。

2. MPI_isendとMPI_irecvに置き換える

デッドロックはブロッキング通信であることが原因なので、ノンブロッキングであるMPI_isendとMPI_irecvに置き換えれば解消できます。

構造体やクラスを通信するには

さて、ここまでテストで扱ってきたmpiコマンドは、すべてmpiデータ型(mpi_integer,mpi_double_precisionなど)を引数に取ってきました。ではここで自分で定義した構造体やクラスなどを通信するにはどうすればよいでしょうか。

実は僕も1行で書けるコードは知りません。mpi_datatype_nullを駆使すれば行けるのかもしれないですが、僕が見たことがあるコードでは、構造体を1種類しか扱っていなかったので、その構造体に対してsendやbcastをそれぞれ定義していました。

これについては次回。

参考サイト

  1. この「abortしない」というのが非常に厄介です。「月曜日に結果を見るつもりで金曜日に計算を回してうまく行ってそうだったのに、月曜日になっても計算が終了しておらず結果も白紙のまま」ということが多々ありました。orz

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?