大量に上るデータの I/O を実行する科学プログラムでは、 速度と効率を高めるために非同期 I/O が必要とされる場合があります。 同期 I/O では、I/O 操作が完了するまでアプリケーションの実行がブロックされます。 非同期 I/O では、I/O 操作をバックグラウンドで実行しながら、 アプリケーションによる処理を続行できます。 処理と I/O 操作を並行して行う能力を使用する場合はアプリケーションを 修正できます。 独立した装置に常駐する複数のファイルに対して、複数の非同期 I/O 操作を同時に 実行することもできます。 この機能を使用するために必要な構文および言語エレメントに関する 詳細な説明については、「XL Fortran ランゲージ・リファレンス」で以下のトピックを参照してください。
非同期データ転送操作を実行すると、以下のステップを指定順序で実行した場合と 同様の効果が得られ、ステップ (6) から (9) が (可能であれば) 非同期で発生します。
Fortran で非同期データ転送を開始するには、Fortran の 非同期 READ および WRITE ステートメントを使用します。 実際のデータ転送が完了したかどうかに関係なく、非同期 I/O ステートメント後も 実行が継続されます。
WAIT ステートメントを使えば、先に開始された非同期 I/O ステートメントと プログラムを同期化することができます。 WAIT ステートメントには 2 つの形式があります。
integer idvar integer, dimension(1000):: a .... READ(unit_number,ID=idvar) a .... WAIT(ID=idvar) ....
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 を大量の小さな項目に対して実行することは控えたほうがよいでしょう。 以下に例をいくつか示します。
不定様式順次ファイルに非同期 I/O を実行する場合、各レコードには異なる長さがあり、 それらの長さはレコードそのものと一緒に格納されるので、効率は劣るものになります。 非同期 I/O の利点を最大に引き出すには、できれば不定様式直接アクセスまたは 不定様式ストリーム・アクセスを使用することをお勧めします。
状況によっては、コンパイラーで一時変数を生成して、I/O 項目式の結果を保持する必要がある場合もあります。 そのような場合、I/O ステートメントでどのようなモードが指定されていても、一時変数に対しては非同期 I/O が 実行されます。 以下に、その事例を示します。
integer a(5), b(3) b = (/1,3,5/) read(99, id=i) a(b)
real a(10) read(99,id=i) a((/1,3,5/))
write(99,id=i) 1000
integer a parameter(a=1000) write(99,id=i) a
type mytype integer a integer b end type mytype write(99,id=i) mytype(4,5)
write(99,id=i) 99+100
write(99,id=i) a+b
external ff real(8) ff write(99,id=i) ff()
write(99,id=i) (/1,2,3,4,5/)
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 ライブラリー libxlf90_r.so では、Fortran I/O ステートメントの 並列実行がサポートされます。 並列ループで I/O ステートメントが入っている、または異なるスレッドから マルチスレッドを作成すると同時に I/O ステートメントを実行するプログラムでは、 このライブラリーを使用する必要があります。 つまり、Fortran 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 ライブラリーによって、スレッドが別の論理装置上で並列操作を行わなくなるということはありません。 つまり、異なる論理装置への並列アクセスは実質的に逐次化されません。
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 操作を実行する順序は予測できません。 XL Fortran スレッド・セーフ I/O ライブラリーでは、順序付けが制御されず、 どのようなスレッドでも特定の論理装置上の I/O ステートメントを実行し、そのロックを取得するものであれば、まず そのスレッドが操作を行うことになります。 したがって、並列 I/O は、少なくとも以下のいずれかが真の場合にのみ使用できます。
これらの事例では、I/O 操作の結果はスレッドが実行される順序に依存していません。 ただし、複数のスレッドから同じ論理装置に並列アクセスを行うと、I/O ライブラリーよる逐次化が行われるため、 パフォーマンスは思ったほど向上しない場合があります。 これらの事例を、以下に例で示します。
do i = 1, 10 write(4, '(i4)', rec = i) a(i) enddo
do i = 1, 9 write(4, '(i4)', pos = 1 + 5 * (i - 1)) a(i) ! We use 5 above because i4 takes 4 file storage ! units + 1 file storage unit for the record marker. enddo
program example use omp_lib integer, parameter :: num_of_threads = 4, max = 5000000 character*10 file_name integer i, file_unit, thread_id integer, dimension(max, 2 * num_of_threads) :: aa call omp_set_num_threads(num_of_threads) !$omp parallel private(file_name, thread_id, file_unit, i) shared(aa) thread_id = omp_get_thread_num() file_name = 'file_' file_name(6:6) = char(ichar('0') + thread_id) file_unit = 10 + thread_id open(file_unit, file = file_name, status = 'old', action = 'read') do i = 1, max read(file_unit, *) aa(i, thread_id * 2 + 1), aa(i, thread_id * 2 + 2) end do close(file_unit) !$omp end parallel end
I/O ライブラリーは、データ転送の並列実行中に、論理装置の状況の検索と更新を同期化します。 I/O ライブラリーが提供する並列性を最大にするには、各 I/O 要求のデータ転送のサイズを増やすことをお勧めします。 そのためには、DO ループを次のように書き直す必要があります。
read(file_unit, *) a(:, thread_id * 2 + 1 : thread_id * 2 + 2) do i = 1, max ! Do something for each element of array 'aa'. end do
real a(100) do i = 1, 10 read(4) a(i) enddo call qsort_(a)
do i = 11, 20 write(i, '(i4)') a(i - 10) enddo
同じ順次アクセス・ファイルでマルチスレッドによる書き込みまたは読み取りを実行する場合、あるいは POS= 指定子を使用せずに同じストリーム・アクセス・ファイルで書き込みまたは読み取りを実行する場合、 レコードの書き込みまたは読み取りの順序は、スレッドがそのファイルで I/O ステートメントを実行する順序に依存しています。 前に述べたとおり、この順序は予測できません。 したがって、アプリケーションの結果で、レコードが定順位でつながっており、書き込みや読み取りを任意に 行えないと判断される場合、その結果は誤ったものになる可能性があります。 このループが並列化されると、数字が逐次実行の結果と同様に 1 から 500 までの順序で出力されることはなくなります。
do i = 1, 500 print *, i enddo
その順序で厳密に配列された数字に依存するアプリケーションは、正確に作動しなくなります。
XL Fortran の実行時オプション multconn=yes を指定すると、同じファイルを同時に複数の論理装置に接続できます。 そのような接続は読み取り (ACCESS='READ') にのみ設定できるため、同じファイルに接続された論理装置への マルチスレッドによるアクセスの結果は予測可能になります。
POSIX シグナル・モデルには基本的に 2 種類のシグナル、つまり同期生成シグナル と 非同期生成シグナル があります。 非マップ式メモリー、保護メモリー、または不良メモリー (SIGSEGV または SIGBUS) への参照、 浮動小数点例外 (SIGFPE)、トラップ命令の実行 (SIGTRAP)、または無許可命令の 実行 (SIGILL) など、スレッドのコードを実行して生成されるシグナルを、同期生成されたシグナルと 言います。 シグナルはプロセス外のイベント、たとえば SIGINT、SIGHUP、SIGQUIT、SIGIO といったイベントによっても生成されます。 そのようなイベントは割り込みと呼ばれます。 割り込みで生成されるシグナルを、非同期生成されたシグナルといいます。
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 ライブラリーにある間にそのスレッドが非同期に取り消される場合、
システムの動作は未定義です。