强制提交到磁盘而不是缓冲区 - 系统资源耗尽

Forcing commit to disk rather than buffer - System resources exhausted

在之前的项目 () 之后,我启动了程序,运行 它在创建小数组(<2 GB RAM)的小数据文件上产生了有效结果。然而,对于更大的项目数据文件,阵列正在接近 10 GB。数据的读取和处理进展顺利

但是当涉及到将数据写回文件时,它没有写入磁盘,而是填满了内存缓冲区,耗尽了系统内存(32 GB RAM),然后机器锁定并重新启动。此结果在机器(笔记本电脑、台式机和虚拟机)之间是一致的,并且与完成该过程的存储设备(SSD、HDD、USB-HDD 或网络 HDD)无关。

所有系统都使用了大约 12-18 个月,i7 处理器,足够的 RAM 和磁盘 space,等等

Google 提供了 FLUSH 等建议,将环境变量 GFORTRAN_UNBUFFERED_ALL 设置为 1(或 'y' 或 'Y')和手动方法,例如关闭文件和然后使用 ACTION='append' 再次打开它以强制写入。

在这些方法中,close-n-open 方法是唯一明显有效的方法,但它只会导致内存填充比其他方法慢,最终系统会再次崩溃。

下面是一个没有任何干扰的写的例子:

      program giant_array

      use iso_fortran_env

      implicit none

      character(len=*), parameter :: csvfmt = '(*(f0.3,:,","))'

      character(20) intval
      character(200) line
      integer(kind=int32) x, y, z, i, cnt
      real(kind=real64), dimension(:,:,:,:), allocatable :: model

      print *,
      print *, "Allocating array and assigning values..."
      print *,

      call random_seed()
      allocate(model(382,390,362,28))
      call random_number(model)

      print *, "Writing array to file..."
      print *,

      open(31, file="test.csv", status='replace', action='write')

      cnt=0

      ! Write array to file:
      do x = 1, 382
        do y = 1, 390
          do z = 1, 362
            write(31, csvfmt) (model(x,y,z,i), i = 1, 28)
            cnt=cnt+1
            if((int(cnt/1000)*1000).eq.cnt) then
              line = " Processing block grade "
              write(intval,'(I12)') cnt
              line = trim(line)//" "//trim(adjustl(intval))//"..."
              write(*,'(A,A)', advance='no') achar(13), trim(line)
            endif
          enddo
        enddo
      enddo

      close(31, status='keep')

      end program

在执行过程中,您会注意到 test.csv 保持在 size=0,直到您终止程序。

即使在打开和关闭之间 'call SLEEP(1)',缓冲区的填充速度也比磁盘写入快,并且在作业完成之前系统崩溃。它也需要很长时间才能完成。

我找到了使用 fsync() 解决此问题的参考资料,但无法编译代码(我想我正在填充命令行参数)。代码如下,来自gcc.gnu.org:

  ! Declare the interface for POSIX fsync function
  interface
    function fsync (fd) bind(c,name="fsync")
    use iso_c_binding, only: c_int
      integer(c_int), value :: fd
      integer(c_int) :: fsync
    end function fsync
  end interface

  ! Variable declaration
  integer :: ret

  ! Opening unit 10
  open (10,file="foo")

  ! ...
  ! Perform I/O on unit 10
  ! ...

  ! Flush and sync
  flush(10)
  ret = fsync(fnum(10))

  ! Handle possible error
  if (ret /= 0) stop "Error calling FSYNC"

虽然其他人也遇到过这个问题,但我无法在任何地方找到解决方案。评论和博客文章表明,即使是 fsync() 方法也不一定总能奏效。

结果每次都是系统崩溃自重启

我猜一定有一种方法可以一次性将大文件写入磁盘,而无需过多的系统规格。

非常感谢。

已更新

代码更新如下,以测试 C++ _commit 语句强制从缓冲区到磁盘。与关闭然后重新打开方法一样工作 - 仍然会杀死机器。完全有可能我的实现还是有问题...

      program giant_array

      use iso_fortran_env
      use iso_c_binding

      implicit none

      ! Declare the interface for WIN32 _commit function
      interface
        function commit (fd) bind(c,name="_commit")
        use iso_c_binding, only: c_int
          integer(c_int), value :: fd
          integer(c_int) :: commit
        end function commit
      end interface

      character(len=*), parameter :: csvfmt = '(*(f0.3,:,","))'

      character(20) intval
      character(200) line
      integer(kind=int32) error
      integer(kind=int32) var, x, y, z, i, cnt
      real(kind=real64), dimension(:,:,:,:), allocatable :: model

      print *,
      print *, "Allocating array and assigning values..."
      print *,

      call random_seed()
      allocate(model(382,390,362,28))
      call random_number(model)

      print *, "Writing array to file..."
      print *,

      open(31, file="test.csv", status='replace', action='write')

      cnt=0

      ! Write array to file:
      do x = 1, 382
        do y = 1, 390
          do z = 1, 362
            write(31, csvfmt) model(x,y,z,:)
            cnt=cnt+1
            if((int(cnt/1000)*1000).eq.cnt) then
              line = " Processing block grade "
              write(intval,'(I12)') cnt
              line = trim(line)//" "//trim(adjustl(intval))//"..."
              write(*,'(A,A)', advance='no') achar(13), trim(line)
              flush(31)
              error=commit(fnum(31))
            endif
          enddo
        enddo
      enddo

      close(31, status='keep')

      end program

如果您使用 Windows 10,如 "windows-10" 标记所示,那么我怀疑您显示的 fsync 代码编译失败的原因是 fsync() 是一个POSIX 函数,但在 Windows 上找不到。我依稀记得 Windows 有一个名为 _commit 的函数,它应该大致相当于 fsync。

这个问题似乎与编译器有关,而不是 OS、Windows 10.

为了进一步测试问题,我安装了个人版的FTN95,修改了代码,重新编译。代码如下:

      program giant_array

      implicit none

      character(len=17), parameter :: csvfmt = '(500(f0.3,:,","))'

      character(20) intval
      character(200) line
      character(1000) outline
      integer(kind=4) x, y, z, cnt
      real(kind=2), dimension(:,:,:,:), allocatable :: model

      write(*,*)
      write(*,*) "Allocating array and assigning values..."
      write(*,*)

      call random_seed()
      allocate(model(28,382,390,362))
      call random_number(model)

      write(*,*) "Writing array to file..."
      write(*,*)

      open(31, file="test.csv", status='replace', action='write')

      ! Write array to file:
      cnt=0
      do x = 1, 382
        do y = 1, 390
          do z = 1, 362
            write(outline, fmt=csvfmt) model(:,x,y,z)
            write(31, '(a)') trim(outline)
            cnt=cnt+1
            if((int(cnt/1000)*1000).eq.cnt) then
              line = " Processing record "
              write(intval,'(I12)') cnt
              line = trim(line)//" "//trim(adjustl(intval))//"..."
              write(*,'(A,A)', advance='no') achar(13), trim(line)
            endif
          enddo
        enddo
      enddo

      close(31, status='keep')

      end program

使用 FTN95 编译的程序对系统没有不利影响,文件将毫无问题地写入磁盘,并且比使用 gfortran(gcc 版本 8.1.0)快得多。虽然这个答案没有解决问题,但它产生了一个有效的结果。

我会继续研究 gfortran 的通用解决方案。