シミュレーションからモニター ファイルを書き込むための次の要件があるとします。
- 通常は 10000 のオーダーで、多数の個々のファイルを書き込む必要があります。
- ファイルは人間が読める形式、つまりフォーマットされた I/O でなければなりません。
- 定期的に、各ファイルに新しい行が追加されます。通常は 50 秒ごと。
- 新しいデータにはほぼ瞬時にアクセスできる必要があるため、大きな手動書き込みバッファはオプションではありません
- 私たちが使用している Lustre ファイル システムは、ほぼ反対の目的で最適化されているように見えます。つまり、少数の大きなファイルへの順次書き込みです。
要件を策定したのは私ではないため、残念ながら要件について議論する意味はありません。上記の前提条件で可能な限り最善の解決策を見つけたいと思います。いくつかの実装をテストするために、少し実用的な例を考え出しました。これが私がこれまでにできる最高のものです:
!===============================================================!
! program to test some I/O implementations for many small files !
!===============================================================!
PROGRAM iotest
use types
use omp_lib
implicit none
INTEGER(I4B), PARAMETER :: steps = 1000
INTEGER(I4B), PARAMETER :: monitors = 1000
INTEGER(I4B), PARAMETER :: cachesize = 10
INTEGER(I8B) :: counti, countf, count_rate, counti_global, countf_global
REAL(DP) :: telapsed, telapsed_global
REAL(DP), DIMENSION(:,:), ALLOCATABLE :: density, pressure, vel_x, vel_y, vel_z
INTEGER(I4B) :: n, t, unitnumber, c, i, thread
CHARACTER(LEN=100) :: dummy_char, number
REAL(DP), DIMENSION(:,:,:), ALLOCATABLE :: writecache_real
call system_clock(counti_global,count_rate)
! allocate cache
allocate(writecache_real(5,cachesize,monitors))
writecache_real = 0.0_dp
! fill values
allocate(density(steps,monitors), pressure(steps,monitors), vel_x(steps,monitors), vel_y(steps,monitors), vel_z(steps,monitors))
do n=1, monitors
do t=1, steps
call random_number(density(t,n))
call random_number(pressure(t,n))
call random_number(vel_x(t,n))
call random_number(vel_y(t,n))
call random_number(vel_z(t,n))
end do
end do
! create files
do n=1, monitors
write(number,'(I0.8)') n
dummy_char = 'monitor_' // trim(adjustl(number)) // '.dat'
open(unit=20, file=trim(adjustl(dummy_char)), status='replace', action='write')
close(20)
end do
call system_clock(counti)
! write data
c = 0
do t=1, steps
c = c + 1
do n=1, monitors
writecache_real(1,c,n) = density(t,n)
writecache_real(2,c,n) = pressure(t,n)
writecache_real(3,c,n) = vel_x(t,n)
writecache_real(4,c,n) = vel_y(t,n)
writecache_real(5,c,n) = vel_z(t,n)
end do
if(c .EQ. cachesize .OR. t .EQ. steps) then
!$OMP PARALLEL DEFAULT(SHARED) PRIVATE(n,number,dummy_char,unitnumber, thread)
thread = OMP_get_thread_num()
unitnumber = thread + 20
!$OMP DO
do n=1, monitors
write(number,'(I0.8)') n
dummy_char = 'monitor_' // trim(adjustl(number)) // '.dat'
open(unit=unitnumber, file=trim(adjustl(dummy_char)), status='old', action='write', position='append', buffered='yes')
write(unitnumber,'(5ES25.15)') writecache_real(:,1:c,n)
close(unitnumber)
end do
!$OMP END DO
!$OMP END PARALLEL
c = 0
end if
end do
call system_clock(countf)
call system_clock(countf_global)
telapsed=real(countf-counti,kind=dp)/real(count_rate,kind=dp)
telapsed_global=real(countf_global-counti_global,kind=dp)/real(count_rate,kind=dp)
write(*,*)
write(*,'(A,F15.6,A)') ' elapsed wall time for I/O: ', telapsed, ' seconds'
write(*,'(A,F15.6,A)') ' global elapsed wall time: ', telapsed_global, ' seconds'
write(*,*)
END PROGRAM iotest
主な機能は次のとおりです。OpenMP の並列化と手動書き込みバッファー。16 スレッドの Lustre ファイル システムでのタイミングの一部を次に示します。
- cachesize=5: I/O の経過時間: 991.627404 秒
- cachesize=10: I/O の経過時間: 415.456265 秒
- cachesize=20: I/O の経過時間: 93.842964 秒
- cachesize=50: I/O の経過時間: 79.859099 秒
- cachesize=100: I/O の経過時間: 23.937832 秒
- cachesize=1000: I/O の経過時間: 10.472421 秒
参照用に、非アクティブ化された HDD 書き込みキャッシュ、16 スレッドを使用したローカル ワークステーション HDD での結果:
- cachesize=1: I/O の経過時間: 5.543722 秒
- cachesize=2: I/O の経過時間: 2.791811 秒
- cachesize=3: I/O の経過時間: 1.752962 秒
- cachesize=4: I/O の経過時間: 1.630385 秒
- cachesize=5: I/O の経過時間: 1.174099 秒
- cachesize=10: I/O の経過時間: 0.700624 秒
- cachesize=20: I/O の経過時間: 0.433936 秒
- cachesize=50: I/O の経過時間: 0.425782 秒
- cachesize=100: I/O の経過時間: 0.227552 秒
ご覧のように、通常の HDD と比較して、Lustre ファイル システムでの実装は依然として驚くほど遅く、I/O オーバーヘッドを許容できる範囲まで削減するには、巨大なバッファ サイズが必要になります。これは、出力が遅れることを意味し、以前に定式化された要件に反しています。別の有望なアプローチは、連続する書き込みの間でユニットを開いたままにすることでした。残念ながら、同時に開くユニットの数は、ルート権限なしでは通常 1024 ~ 4096 に制限されています。ファイル数がこの制限を超える可能性があるため、これはオプションではありません。
要件を満たしながら、I/O オーバーヘッドをさらに削減するにはどうすればよいでしょうか?
編集 1 Gilles との議論から、光沢ファイル システムは通常のユーザー権限でも微調整できることがわかりました。そこで、提案どおりにストライプ数を 1 に設定してみました (これは既にデフォルト設定でした)。ストライプ サイズをサポートされている最小値の 64k (デフォルト値は 1M) に減らしました。ただし、私のテスト ケースでは、これによって I/O パフォーマンスが向上しませんでした。より適切なファイル システム設定に関する追加のヒントがある場合は、お知らせください。