在循环读取大文件时如何防止 RubyMotion 中的内存泄漏

How to prevent memory leaks in RubyMotion when reading large files in loops

RubyMotion 应该进行自动内存管理:

RubyMotion provides automatic memory management; you do not need to reclaim unused objects.

但是,当循环读取大文件时,我遇到了巨大的内存泄漏:每秒数百 MB/s,就好像我的读取缓冲区从未被释放过一样。

如果我在每个循环的读取缓冲区上使用 release,泄漏大部分都会消失。问题是,release 使应用程序在循环结束时崩溃。

  def readBigBinaryFile(file)
#   PURE RUBY WOULD BE
#   source=File.open(file,'r')
    source =NSFileHandle.fileHandleForReadingAtPath(file)
    buffer_size=4096
    offset=0
    size=File.size(file)
    while ( offset + buffer_size ) <= size
#      PURE RUBY WOULD BE
#      source.seek(offset) 
#      abuffer = source.read( buffer_size )
#      abuffer=abuffer.to_s

      source.seekToFileOffset(offset)
      abuffer = source.readDataOfLength(buffer_size)
      offset+=buffer_size
      @dataPointer ||= Pointer.new(:uchar,4) 
      abuffer.getBytes(@dataPointer, length: 4)
      # memory leaks very rapidly in GBs if we don't release the buffer…
      # but this relase action will make the application crash once the doSomething lookp is finished
      abuffer.release
    end
    source.closeFile
    return false
  end

循环是:

x=0
while x < 10000
  my_scan_binary_instance=Scan_binary.new()        result=my_scan_binary_instance.readBigBinaryFile(NSBundle.mainBundle.pathForResource("sample1MBfile", ofType:"img"))
  puts result.to_s
  x+=1
end
puts "if we have used 'abuffer.release', we are going to crash now"

我测试了纯Ruby实现,完全没有内存泄漏,也不需要释放调用。

我发现“How do I prevent memory leak when I load large pickle files in a for loop?”关于 Python 循环中的内存泄漏,但是接受的解决方案在 while block 的开头执行 abuffer=nil readBigBinaryFile 没用。

这是 RubyMotion 的自动内存管理中的错误,还是预料之中的? 最重要的是,如何在不增加 RubyMotion 应用程序的内存使用量的情况下循环读取大文件?

我创建了 a gist with the working pure Ruby implementation, and a repo 重现崩溃的示例应用程序。

我不知道这是否能解决问题,但也许可以尝试更换

abuffer = source.readDataOfLength(buffer_size)

abuffer = WeakRef.new(source.readDataOfLength(buffer_size))

有关 WeakRef 的信息,请参阅“4.1. Weak References”。

进行此更改意味着您还将删除对 abuffer.release.

的调用

尝试将循环体包裹在 autorelease_pool do ... end 中。这应该导致 每个循环要释放的自动释放对象。在循环结束时将 nil 分配给 abuffer 将允许释放缓冲区内存,因为将不再有对它的引用。

while ( offset + buffer_size ) <= size
  autorelease_pool do
    source.seekToFileOffset(offset)
    abuffer = source.readDataOfLength(buffer_size)
    offset+=buffer_size
    @dataPointer ||= Pointer.new(:uchar,4) 
    abuffer.getBytes(@dataPointer, length: 4)

    abuffer = nil
  end
end