10

考虑这个片段:

l = []

while 1
  l << 'a random 369-characterish string'
end
^C
# ran this for maybe 4 seconds, and it had 27 million entries in l. memory
# usage was 1.6 GB.

l = nil

# no change in memory usage

GC.start

# memory usage drops a relatively small amount, from 1.6 GB to 1.39 GB.

我正在将数百万个元素推入/通过 Ruby 的数据结构,并且存在一些严重的内存问题。这个例子表明,即使在没有对现存对象的引用的情况下,Ruby 也不会让 [大部分] 它离开,即使在显式调用GC.start.

我在现实生活中使用的对象总共将数百万个元素推入散列,但散列用作临时查找表,并在某个循环完成后清零。然而,这个查找表中的内存显然永远不会被释放,这会极大地减慢我的应用程序的速度,因为 GC 在每个周期都有数百万个失效对象需要分析。我正在研究sparsehashgem 的解决方法,但这似乎不是一个棘手的问题,Ruby 运行时应该像那样窒息。明确删除引用,明确收集和处置对象。谁能帮我弄清楚为什么这没有发生?

我已经尝试过l.delete_if { |x| true}freenode 上#ruby 中用户的建议,但这确实很慢,而且似乎也从未导致明显的内存释放。

使用ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux].

编辑:

为了比较,这是一个运行python3

l = []

while 1:
    l.append('a random 369-characterish string')
^C
# 31,216,082 elements; 246M memory usage.

l = []
# memory usage drops to 8K (0% of system total)

在 python2 上的测试显示几乎相同的结果。

我不确定这是否足以将其视为 MRI 中的实施缺陷,或者它是否只是归因于 GC 的不同方法。无论哪种方式,Python 似乎更适合将总共数百万个元素通过数据结构推送并定期将结构归零的用例(就像临时查找表可能做的那样)。

看起来这确实应该是一个简单的。:\

4

1 回答 1

1

有点骇人听闻,但您可以尝试fork将操作作为一个单独的过程关闭。该进程将在共享内存空间中运行;当它终止时,内存将被释放。

正如@Sergio Tulentsev 在评论中指出的那样,Ruby 可能不会将内存释放回内核。

这个 Ruby/Unix 邮件列表对话详细描述了这一点:避免系统调用

此外,这篇博文将分叉描述为 Rails 中内存管理的解决方案:使用 fork() 和 copy-on-write 在 Ruby on Rails 中保存内存。虽然,我认为在 Ruby 2 出现之前,Ruby 不会支持写时复制。

于 2012-09-19T14:46:28.010 回答