簡體   English   中英

在fortran中寫入大型數組文件的最佳方法是什么? 文字與其他

[英]Best way to write a large array to file in fortran? Text vs Other

我想知道將大型fortran數組(5000 x 5000實數單精度數字)寫入文件的最佳方法。 我試圖保存數值計算的結果供以后使用,因此不需要重復。 從計算5000 x 5000 x 4bytes每個數字是100 Mb,是否可以以100Mb的形式保存? 有沒有辦法將fortran數組保存為二進制文件並將其讀回以供以后使用?

我注意到將數字保存到文本文件會產生比保存的數據類型大得多的文件。 這是因為數字被保存為字符嗎?

我熟悉寫入文件的唯一方法是

open (unit=41, file='outfile.txt')

do  i=1,len
    do j=1,len

        write(41,*) Array(i,j)
    end do
end do

雖然我認為有更好的方法可以做到這一點。 如果有人能指出我的一些資源或例子來批准我有效地(在內存方面)編寫和閱讀更大文件的能力,這將是很好的。 謝謝!

以二進制形式寫入數據文件,除非您實際上要讀取輸出 - 並且您不會讀取250萬個元素的數組。

使用二進制文件的原因是三重的,重要性降低了:

  • 准確性
  • 性能
  • 數據大小

准確性問題可能是最明顯的。 當您將(二進制)浮點數轉換為十進制數字的字符串表示時,您不可避免地會在某個時刻截斷。 如果你確定當你將文本值讀回浮點值時,你肯定會獲得相同的值,那就沒關系了。 但這實際上是一個微妙的問題,需要仔細選擇您的格式。 使用默認格式,各種編譯器以不同的質量執行此任務。 這篇博文是從游戲程序員的角度編寫的,可以很好地解決這些問題。

讓我們考慮一個小程序,對於各種格式,將單精度實數寫入字符串,然后再次讀回來,跟蹤它遇到的最大錯誤。 我們將以機器epsilon為單位從0到1。 代碼如下:

program testaccuracy

    character(len=128) :: teststring
    integer, parameter :: nformats=4
    character(len=20), parameter :: formats(nformats) =   &
        [ '( E11.4)', '( E13.6)', '( E15.8)', '(E17.10)' ]
    real, dimension(nformats) :: errors

    real :: output, back
    real, parameter :: delta=epsilon(output)
    integer :: i

    errors = 0
    output = 0
    do while (output < 1)
        do i=1,nformats
            write(teststring,FMT=formats(i)) output
            read(teststring,*) back
            if (abs(back-output) > errors(i)) errors(i) = abs(back-output)
        enddo
        output = output + delta
    end do

    print *, 'Maximum errors: '
    print *, formats
    print *, errors

    print *, 'Trying with default format: '

    errors = 0
    output = 0
    do while (output < 1)
        write(teststring,*) output
        read(teststring,*) back
        if (abs(back-output) > errors(1)) errors(1) = abs(back-output)
        output = output + delta
    end do

    print *, 'Error = ', errors(1)

end program testaccuracy

當我們運行它時,我們得到:

$ ./accuracy 
 Maximum errors: 
 ( E11.4)            ( E13.6)            ( E15.8)            (E17.10)            
  5.00082970E-05  5.06639481E-07  7.45058060E-09   0.0000000    
 Trying with default format: 
 Error =   7.45058060E-09

請注意,即使使用小數點后8位數的格式 - 我們可能認為這將是充足的,因為單精度實數僅精確到6-7位小數 - 我們不會得到精確的副本,大約1e -8。 而這個編譯器的默認格式,並沒有給我們准確的往返浮點值; 引入了一些錯誤! 如果你是一個視頻游戲程序員,那么這種准確度可能就足夠了。 但是,如果你正在對湍流流體進行時間相關的模擬,那么這可能絕對不行,特別是如果對引入誤差的位置存在偏差,或者錯誤發生在應該是一個守恆量的情況下。

請注意,如果您嘗試運行此代碼,您會注意到完成需要很長時間。 這可能是因為,令人驚訝的是,性能是浮點數文本輸出的另一個真正問題。 考慮以下簡單程序,它只是將5000×5000實數組的示例寫成文本和未格式化的二進制文件:

program testarray
    implicit none
    integer, parameter :: asize=5000
    real, dimension(asize,asize) :: array

    integer :: i, j
    integer :: time, u

    forall (i=1:asize, j=1:asize) array(i,j)=i*asize+j

    call tick(time)
    open(newunit=u,file='test.txt')
    do i=1,asize
        write(u,*) (array(i,j), j=1,asize)
    enddo
    close(u)
    print *, 'ASCII: time = ', tock(time)

    call tick(time)
    open(newunit=u,file='test.dat',form='unformatted')
    write(u) array
    close(u)
    print *, 'Binary: time = ', tock(time)


contains
    subroutine tick(t)
        integer, intent(OUT) :: t
        call system_clock(t)
    end subroutine tick

    ! returns time in seconds from now to time described by t 
    real function tock(t)
        integer, intent(in) :: t
        integer :: now, clock_rate
        call system_clock(now,clock_rate)
        tock = real(now - t)/real(clock_rate)
    end function tock

end program testarray

以下是用於寫入磁盤或ramdisk的時序輸出:

Disk:
 ASCII: time =    41.193001    
 Binary: time =   0.11700000    
Ramdisk
 ASCII: time =    40.789001    
 Binary: time =   5.70000000E-02

請注意,寫入磁盤時,二進制輸出的速度是ASCII的352倍 ,而ramdisk的速度接近700倍。 這有兩個原因 - 一個是你可以一次寫出所有數據,而不是必須循環; 另一個是生成浮點數的字符串十進制表示是一個令人驚訝的微妙操作,需要對每個值進行大量計算。

最后是數據大小; 上面示例中的文本文件(在我的系統上)出現到二進制文件大小的4倍左右。

現在,二進制輸出存在實際問題。 特別是,原始Fortran(或者,就此而言,C)二進制輸出非常脆弱。 如果您更改平台,或者您的數據大小發生變化,您的輸出可能不再有任何好處。 將新變量添加到輸出將破壞文件格式,除非您始終在文件末尾添加新數據,並且您無法提前知道從協作者(可能是誰)獲得的二進制blob中的變量你,三個月前)。 通過使用像NetCDF這樣的庫來避免二進制輸出的大多數缺點, NetCDF編寫的自描述二進制文件比原始二進制文件更具“未來證明”。 更好的是,因為它是一個標准,許多工具讀取NetCDF文件。

互聯網上有許多NetCDF教程; 我們在這里 使用NetCDF的簡單示例給出了與原始二進制文件類似的時間:

$ ./array 
 ASCII: time =    40.676998    
 Binary: time =   4.30000015E-02
 NetCDF: time =   0.16000000  

但是給你一個很好的自描述文件:

$ ncdump -h test.nc
netcdf test {
dimensions:
    X = 5000 ;
    Y = 5000 ;
variables:
    float Array(Y, X) ;
        Array:units = "ergs" ;
}

和文件大小大致相同的原始二進制文件:

$ du -sh test.*
96M test.dat
96M test.nc
382M    test.txt

代碼如下:

program testarray
    implicit none
    integer, parameter :: asize=5000
    real, dimension(asize,asize) :: array

    integer :: i, j
    integer :: time, u

    forall (i=1:asize, j=1:asize) array(i,j)=i*asize+j

    call tick(time)
    open(newunit=u,file='test.txt')
    do i=1,asize
        write(u,*) (array(i,j), j=1,asize)
    enddo
    close(u)
    print *, 'ASCII: time = ', tock(time)

    call tick(time)
    open(newunit=u,file='test.dat',form='unformatted')
    write(u) array
    close(u)
    print *, 'Binary: time = ', tock(time)

    call tick(time)
    call writenetcdffile(array)
    print *, 'NetCDF: time = ', tock(time)


contains
    subroutine tick(t)
        integer, intent(OUT) :: t
        call system_clock(t)
    end subroutine tick

    ! returns time in seconds from now to time described by t 
    real function tock(t)
        integer, intent(in) :: t
        integer :: now, clock_rate
        call system_clock(now,clock_rate)
        tock = real(now - t)/real(clock_rate)
    end function tock

    subroutine writenetcdffile(array)
        use netcdf
        implicit none
        real, intent(IN), dimension(:,:) :: array

        integer :: file_id, xdim_id, ydim_id
        integer :: array_id
        integer, dimension(2) :: arrdims
        character(len=*), parameter :: arrunit = 'ergs'

        integer :: i, j
        integer :: ierr

        i = size(array,1)
        j = size(array,2)

        ! create the file
        ierr = nf90_create(path='test.nc', cmode=NF90_CLOBBER, ncid=file_id)

        ! define the dimensions
        ierr = nf90_def_dim(file_id, 'X', i, xdim_id)
        ierr = nf90_def_dim(file_id, 'Y', j, ydim_id)

        ! now that the dimensions are defined, we can define variables on them,...
        arrdims = (/ xdim_id, ydim_id /)
        ierr = nf90_def_var(file_id, 'Array',  NF90_REAL, arrdims, array_id)

        ! ...and assign units to them as an attribute 
        ierr = nf90_put_att(file_id, array_id, "units", arrunit)

        ! done defining
        ierr = nf90_enddef(file_id)

        ! Write out the values
        ierr = nf90_put_var(file_id, array_id, array)

        ! close; done
        ierr = nf90_close(file_id)
    return
    end subroutine writenetcdffile
end program testarray

打開文件以進行“未格式化”的讀寫,並在不提供格式的情況下讀取和寫入數據,如下面的程序所示。

program xunformatted
integer, parameter :: n = 5000, inu = 20, outu = 21
real               :: x(n,n)
integer            :: i
character (len=*), parameter :: out_file = "temp_num"
call random_seed()
call random_number(x)
open (unit=outu,form="unformatted",file=out_file,action="write")
do i=1,n
   write (outu) x(i,:) ! write one row at a time
end do
print*,"sum(x) =",sum(x)
close (outu)
open (unit=inu,form="unformatted",file=out_file,action="read")
x = 0.0
do i=1,n
   read (inu) x(i,:)   ! read one row at a time
end do
print*,"sum(x) =",sum(x)
end program xunformatted

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM