非同期 I/O

大量に上るデータの I/O を実行する科学プログラムでは、 速度と効率を高めるために非同期 I/O が必要とされる場合があります。 同期 I/O では、I/O 操作が完了するまでアプリケーションの実行がブロックされます。 非同期 I/O では、I/O 操作をバックグラウンドで実行しながら、 アプリケーションによる処理を続行できます。 処理と I/O 操作を並行して行う能力を使用する場合はアプリケーションを 修正できます。 独立した装置に常駐する複数のファイルに対して、複数の非同期 I/O 操作を同時に 実行することもできます。 この機能を使用するために必要な構文および言語エレメントに関する 詳細な説明については、「XL Fortran ランゲージ・リファレンス」で以下のトピックを参照してください。

非同期データ転送操作の実行

非同期データ転送操作を実行すると、以下のステップを指定順序で実行した場合と 同様の効果が得られ、ステップ (6) から (9) が (可能であれば) 非同期で発生します。

  1. データ転送の方法を判別します。
  2. 装置を識別します。
  3. 形式がある場合はその形式を明確化します。
  4. エラー条件、ファイルの終わり条件、またはレコードの終わり条件が発生したかどうかを 判別します。
  5. データ転送ステートメント中の IOSTAT= 指定子で 指定した変数が定義されるようにします。
  6. データ転送に先立ってファイルを位置決めします。
  7. 入出力リスト (もしあれば) で指定されたファイルとエンティティーとの間で データを転送します。
  8. エラー条件、ファイルの終わり条件、またはレコードの終わり条件が発生したかどうかを 判別します。
  9. データ転送後にファイルを位置決めします。
  10. WAIT ステートメント中の IOSTAT= および SIZE= 指定子で 指定された変数があれば、その変数を定義するようにします。

使用法

Fortran で非同期データ転送を開始するには、Fortran の 非同期 READ および WRITE ステートメントを使用します。 実際のデータ転送が完了したかどうかに関係なく、非同期 I/O ステートメント後も 実行が継続されます。

WAIT ステートメントを使えば、先に開始された非同期 I/O ステートメントと プログラムを同期化することができます。 WAIT ステートメントには 2 つの形式があります。

  1. DONE= 指定子のない WAIT ステートメントでは 以下のように、対応する非同期 I/O ステートメントが完了するまで、 WAIT ステートメントが実行を一時停止します。
    	integer idvar
    	integer, dimension(1000):: a
    	....
    	READ(unit_number,ID=idvar) a
    	....
    	WAIT(ID=idvar)
    	....
    
  2. DONE= 指定子がある WAIT ステートメントでは以下のように、WAIT ステートメントが 非同期 I/O ステートメントの完了状況を戻します。
    	integer idvar
    	logical done
    	integer, dimension(1000):: a
    	....
    	READ(unit_number,ID=idvar) a
    	....
    	WAIT(ID=idvar, DONE=done)
    	....
    

    DONE= 指定子で指定した変数は、対応する非同期 I/O ステートメントが完了している場合は、真に 設定されます。 それ以外の場合は、偽に設定されます。

実際のデータ転送は、以下の場合に起こると思われます。

非同期 I/O の性質上、要求の実際の完了時刻は予測できません。

Fortran 非同期 READ および WRITE ステートメントは、ID= 指定子を使って指定します。 非同期 READ または WRITE ステートメントによる ID= 指定子の値セットは、対応する WAIT ステートメント中の ID= 指定子と同じでなければなりません。 関連した非同期 I/O ステートメントが完了するまでこの値を保持しておく責任はプログラマーにあります。

以下のプログラムでは、有効な非同期 WRITE ステートメントを示します。

        program sample0
        integer, dimension(1000):: a
        integer idvar
        a = (/(i,i=1,1000)/)
        WRITE(10,ID=idvar) a
        WAIT(ID=idvar)
        end

XL Fortran では、関連する WAIT ステートメントの前の非同期 I/O 識別子の値が破棄されるため、以下の プログラムは無効です。

        program sample1
        integer, dimension(1000):: a
        integer idvar
        a = (/(i,i=1,1000)/)
        WRITE(10,ID=idvar) a
        idvar = 999     ! Valid id is destroyed.
        WAIT(ID=idvar)
        end

非同期 I/O を使用するアプリケーションは一般に、I/O 操作を並列処理してパフォーマンスを向上させます。 以下に簡単な例を示します。

        program sample2
        integer  (kind=4), parameter :: isize=1000000, icol=5
        integer  (kind=4) :: i, j, k
        integer  (kind=4), dimension(icol) :: handle
        integer  (kind=4), dimension(isize,icol), static :: a, a1
 
!
!       Opens the file for both synchronous and asynchronous I/O.
!
        open(20,form="unformatted",access="direct", &
           status="scratch", recl=isize*4,asynch="yes")
 
!
!       This loop overlaps the initialization of a(:,j) with
!       asynchronous write statements.
!
!       NOTE: The array is written out one column at a time.
!             Since the arrays in Fortran are arranged in column
!             major order, each WRITE statement writes out a
!             contiguous block of the array.
!
        do 200 j = 1, icol
           a(:,j) = (/ (i*j,i=1,isize) /)
           write(20, id=handle(j), rec=j) a(:,j)
200     end do
 
!
!       Wait for all writes to complete before reading.
!
        do 300 j = 1, icol
           wait(id=handle(j))
300     end do
 
!
!       Reads in the first record.
!
        read(20, id=handle(1), rec=1) a1(:,1)
 
        do 400 j = 2, icol
           k = j - 1
!
!          Waits for a previously initiated read to complete.
!
           wait(id=handle(k))
!
!          Initiates the next read immediately.
!
           read(20, id=handle(j), rec=j) a1(:,j)
!
!          While the next read is going on, we do some processing here.
!
           do 350 i = 1, isize
              if (a(i,k) .ne. a1(i,k)) then
                 print *, "(",i,",",k,") &
                 &  expected ", a(i,k), " got ", a1(i,k)
              end if
350        end do
400     end do
 
!
!       Finish the last record.
!
        wait(id=handle(icol))
 
        do 450 i = 1, isize
           if (a(i,icol) .ne. a1(i,icol)) then
              print *, "(",i,",",icol,") &
              &  expected ", a(i,icol), " got ", a1(i,icol)
           end if
450     end do
 
        close(20)
        end

パフォーマンス

非同期 I/O の利点を最大に生かすには、非同期 I/O を大量の連続データ項目に対して実行することをお勧めします。

大量の小さな項目に対して非同期 I/O を実行することも可能ですが、パフォーマンス自体は悪化します。 これは、非同期 I/O で各項目を保守するために余分の処理オーバーヘッドが必要とされるという状況に起因します。 非同期 I/O を大量の小さな項目に対して実行することは控えたほうがよいでしょう。 以下に例をいくつか示します。

  1. WRITE(unit_number, ID=idvar) a1(1:100000000:2)
  2. WRITE(unit_number, ID=idvar) (a2(i,j),j=1,100000000)

不定様式順次ファイルに非同期 I/O を実行する場合、各レコードには異なる長さがあり、 それらの長さはレコードそのものと一緒に格納されるので、効率は劣るものになります。 非同期 I/O の利点を最大に引き出すには、できれば不定様式直接アクセスまたは 不定様式ストリーム・アクセスを使用することをお勧めします。

コンパイラーで生成する一時 I/O 項目

状況によっては、コンパイラーで一時変数を生成して、I/O 項目式の結果を保持する必要がある場合もあります。 そのような場合、I/O ステートメントでどのようなモードが指定されていても、一時変数に対しては非同期 I/O が 実行されます。 以下に、その事例を示します。

  1. READ で、入力項目にベクトル添え字を持つ配列が現れる場合
    1.         integer a(5), b(3)
       
              b = (/1,3,5/)
              read(99, id=i) a(b)
      
    2.         real a(10)
              read(99,id=i) a((/1,3,5/))
      
  2. WRITE で、出力項目が定数の式、または特定の派生型の定数である場合
    1.      write(99,id=i) 1000
      
    2.      integer a
           parameter(a=1000)
       
           write(99,id=i) a
      
    3.      type mytype
           integer a
           integer b
           end type mytype
       
           write(99,id=i) mytype(4,5)
      
  3. WRITE で、出力項目が一時変数である場合
    1.      write(99,id=i) 99+100
      
    2.      write(99,id=i) a+b
      
    3.      external ff
           real(8) ff
       
           write(99,id=i) ff()
      
  4. WRITE で、出力項目が配列コンストラクターである式の場合
         write(99,id=i) (/1,2,3,4,5/)
    
  5. WRITE で、出力項目がスカラー型配列である場合
         integer a(5),b(5)
         write(99,id=i) a+b
    

エラー処理

非同期データ転送では、データ転送ステートメントの実行中または後続のデータ転送中にエラーまたは ファイルの終わり条件が発生する場合があります。 プログラムの終了時にこれらの条件が発生しない場合、プログラマーは、データ転送または WAIT ステートメントの突き合わせで ERR=END=、および IOSTAT= 指定子を 使ってそれらの条件を検出できます。

IOSTAT= 指定子も ERR= 指定子も入っていない I/O ステートメントの実行中、またはその 後続データ転送中にエラー条件が発生した場合、プログラムの実行が終了します。 回復可能エラーの場合は、IOSTAT= および ERR= 指定子が存在しなけれ ば、err_recovery 実行時オプションが no に設定されている場合、プログラムは終了します。 err_recovery 実行時オプションが yes に設定されていれば、回復処置が発生し、プログラムは 続行します。

非同期データ転送ステートメントが原因で以下の状況が発生する場合、ID= 値が定義されていない ので、WAIT ステートメントは許可されません。

XL Fortran スレッド・セーフ I/O ライブラリー

XL Fortran スレッド・セーフ I/O ライブラリー libxlf90_r.so では、Fortran I/O ステートメントの 並列実行がサポートされます。 並列ループで I/O ステートメントが入っている、または異なるスレッドから マルチスレッドを作成すると同時に I/O ステートメントを実行するプログラムでは、 このライブラリーを使用する必要があります。 つまり、Fortran I/O を並列実行する場合、期待どおりの結果を得るには、 アプリケーションをこのライブラリーとリンクしなければなりません。

I/O 操作の同期化

並列実行中には、マルチスレッドで I/O 操作が同じファイル上で同時に実行される場合があります。 操作が同期化されていなければ、これらの I/O 操作の結果は切り捨てられるか結合される、またはその両方が当てはまる 場合があり、アプリケーションは間違った結果を作成し、壊れる場合さえあります。 XL Fortran スレッド・セーフ I/O ライブラリーは並列アプリケーションの I/O 操作を同期化します。 同期化は I/O ライブラリー内で実行され、アプリケーション・プログラムに対して透過的です。 同期化の目的は、個々の I/O 操作の整合性と正確さを保証することにあります。 ただし、スレッド・セーフ I/O ライブラリーは、スレッドが I/O ステートメントを実行する順序を制御しません。 したがって、並列 I/O 操作では、読み取ったり書き出したりするレコードの順序を予測することはできません。 詳細については、並列 I/O の問題を参照してください。

外部ファイル

外部ファイルの場合、同期化は装置単位で実行されます。 XL Fortran スレッド・セーフ I/O ライブラリーでは必ず、特定の論理装置にアクセスするスレッドが 1 つだけになり、いくつかの スレッドが互いに干渉することがなくなります。 スレッドがある装置上で I/O 操作を実行しているときに同じ装置上で I/O 操作を実行しようとする別のスレッドは、最初の スレッドが操作を終了するまで待機しなければなりません。 したがって、同じ装置上のマルチスレッドによる I/O ステートメントの実行は逐次化されます。 しかし、スレッド・セーフ I/O ライブラリーによって、スレッドが別の論理装置上で並列操作を行わなくなるということはありません。 つまり、異なる論理装置への並列アクセスは実質的に逐次化されません。

同期化における I/O の機能性

XL Fortran スレッド・セーフ I/O ライブラリーは、論理装置へのアクセスを同期化するために内部ロックを設定します。 これによって、Fortran プログラムで実行される I/O 操作に機能面での影響が及ぶことはありません。 また、Fortran I/O ステートメントの操作性に付加的な制限が課されることもありません。 ただし、非同期的に起動されるシグナル・ハンドラーで I/O ステートメントを使用する場合はこの限りではありません。 詳細については、シグナル・ハンドラーでの I/O ステートメントの使用を参照してください。

Fortran の規格では、関数参照によって別の I/O ステートメントが実行される場合、I/O ステートメントのどの場所でも、 式の中に関数参照を入れることが禁止されています。 この制限は引き続き XL Fortran スレッド・セーフ I/O ライブラリーに適用されます。

並列 I/O の問題

並列スレッドが I/O 操作を実行する順序は予測できません。 XL Fortran スレッド・セーフ I/O ライブラリーでは、順序付けが制御されず、 どのようなスレッドでも特定の論理装置上の I/O ステートメントを実行し、そのロックを取得するものであれば、まず そのスレッドが操作を行うことになります。 したがって、並列 I/O は、少なくとも以下のいずれかが真の場合にのみ使用できます。

これらの事例では、I/O 操作の結果はスレッドが実行される順序に依存していません。 ただし、複数のスレッドから同じ論理装置に並列アクセスを行うと、I/O ライブラリーよる逐次化が行われるため、 パフォーマンスは思ったほど向上しない場合があります。 これらの事例を、以下に例で示します。

同じ順次アクセス・ファイルでマルチスレッドによる書き込みまたは読み取りを実行する場合、あるいは POS= 指定子を使用せずに同じストリーム・アクセス・ファイルで書き込みまたは読み取りを実行する場合、 レコードの書き込みまたは読み取りの順序は、スレッドがそのファイルで I/O ステートメントを実行する順序に依存しています。 前に述べたとおり、この順序は予測できません。 したがって、アプリケーションの結果で、レコードが定順位でつながっており、書き込みや読み取りを任意に 行えないと判断される場合、その結果は誤ったものになる可能性があります。 このループが並列化されると、数字が逐次実行の結果と同様に 1 から 500 までの順序で出力されることはなくなります。

     do i = 1, 500
       print *, i
     enddo

その順序で厳密に配列された数字に依存するアプリケーションは、正確に作動しなくなります。

XL Fortran の実行時オプション multconn=yes を指定すると、同じファイルを同時に複数の論理装置に接続できます。 そのような接続は読み取り (ACCESS='READ') にのみ設定できるため、同じファイルに接続された論理装置への マルチスレッドによるアクセスの結果は予測可能になります。

シグナル・ハンドラーでの I/O ステートメントの使用

POSIX シグナル・モデルには基本的に 2 種類のシグナル、つまり同期生成シグナル非同期生成シグナル があります。 非マップ式メモリー、保護メモリー、または不良メモリー (SIGSEGV または SIGBUS) への参照、 浮動小数点例外 (SIGFPE)、トラップ命令の実行 (SIGTRAP)、または無許可命令の 実行 (SIGILL) など、スレッドのコードを実行して生成されるシグナルを、同期生成されたシグナルと 言います。 シグナルはプロセス外のイベント、たとえば SIGINTSIGHUPSIGQUITSIGIO といったイベントによっても生成されます。 そのようなイベントは割り込みと呼ばれます。 割り込みで生成されるシグナルを、非同期生成されたシグナルといいます。

XL Fortran スレッド・セーフ I/O ライブラリーは、非同期シグナル・アンセーフです。 つまり、非同期生成シグナルであるために入力されるシグナル・ハンドラーでは XL Fortran I/O ステートメントを 使用できません。 I/O ステートメントに割り込むシグナル・ハンドラーから XL Fortran I/O ステートメントが呼び出される場合、 システムの動作は未定義です。 ただし、同期シグナルのシグナル・ハンドラーで I/O ステートメントを使用することは問題ありません。

アプリケーションでシグナル・ハンドラーの同期入力はないことが保証される場合もあります。 たとえば、特定の部分の認知コードの実行を除いて、シグナルをマスクするアプリケーションがあります。 そのような状況では、シグナルが I/O ステートメントや、非同期シグナル・アンセーフ機能に割り込むことは ないことが知られています。 したがって、非同期シグナル・ハンドラーでは引き続き Fortran I/O ステートメントを使用できます。

非同期シグナルをさらに簡単かつ安全に処理する方法は、すべてのスレッドでシグナルをブロックし、それらの スレッドを 1 つ以上の別個のスレッドで明示的に待機 (sigwait() を使用) することです。 この方法による利点は、handler スレッドが、Fortran I/O ステートメントに加えて、 他の非同期シグナル・アンセーフ・ルーチンを使用できることです。

非同期スレッドの取り消し

スレッドで非同期スレッドの取り消しを使用可能にすると、取り消し要求がある場合はその要求がただちに 処理されます。 XL Fortran スレッド・セーフ I/O ライブラリーは、非同期スレッドの取り消しに対してセーフではありません。 スレッドが XL Fortran スレッド・セーフ I/O ライブラリーにある間にそのスレッドが非同期に取り消される場合、 システムの動作は未定義です。 IBM Copyright 2003