4

シミュレーションからモニター ファイルを書き込むための次の要件があるとします。

  • 通常は 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 パフォーマンスが向上しませんでした。より適切なファイル システム設定に関する追加のヒントがある場合は、お知らせください。

4

1 に答える 1