【问题标题】:Ruby Memory ManagementRuby 内存管理
【发布时间】:2008-10-08 04:59:05
【问题描述】:

我使用 Ruby 已经有一段时间了,我发现对于更大的项目,它会占用相当多的内存。在 Ruby 中减少内存使用的最佳实践是什么?

  • 请让每个答案都有一个“最佳实践”,并让社区投票赞成。

【问题讨论】:

  • 我认为最重要的是:知道为什么要使用这么多内存,以及这些内存都去了哪里。也许有一个工具。

标签: ruby memory memory-management coding-style


【解决方案1】:

在处理大量 ActiveRecord 对象时要非常小心...在循环中处理这些对象时,如果在每次迭代中都使用 ActiveRecord 的 has_many、belongs_to 等加载它们的相关对象 - 内存使用量会增长很多,因为属于数组的每个对象都会增长...

以下技术对我们帮助很大(简化示例):

students.each do |student|
  cloned_student = student.clone
  ...
  cloned_student.books.detect {...}
  ca_teachers = cloned_student.teachers.detect {|teacher| teacher.address.state == 'CA'}
  ca_teachers.blah_blah
  ...
  # Not sure if the following is necessary, but we have it just in case...
  cloned_student = nil
end

在上面的代码中,“cloned_student”是增长的对象,但由于它在每次迭代结束时“无效”,这对于大量学生来说不是问题。如果我们不做“克隆”,循环变量“学生”会增长,但由于它属于一个数组 - 只要数组对象存在,它所使用的内存就永远不会释放。

不同的方法也可以:

students.each do |student|
  loop_student = Student.find(student.id) # just re-find the record into local variable.
  ...
  loop_student.books.detect {...}
  ca_teachers = loop_student.teachers.detect {|teacher| teacher.address.state == 'CA'}
  ca_teachers.blah_blah
  ...
end

在我们的生产环境中,我们有一个后台进程无法完成一次,因为 8Gb 的 RAM 不够用。在这个小改动之后,它使用不到 1Gb 来处理相同数量的数据...

【讨论】:

  • Alex,这是个好技巧,我已经测试过了,它使我们的两个进程减少到 1500 万。但我不确定一件事,为什么学生不发布,因为它应该总是在每次迭代中获得新的价值?
  • 学生对象没有被释放,因为它仍然属于一个学生数组,即使它是一个循环变量——它在每次迭代中都没有获得新值,而是用作指针(有点)到数组中的项目。因此,如果学生的大小增加,则数组的大小也会增加。这就是为什么如果您克隆学生 - 您实际上是在处理一个单独的对象,该对象实际上在每次迭代时都会被释放。
  • 一段时间以来,我们一直在处理相同的长时间运行的内存占用后台作业问题。这可能会削减它!谢谢亚历克斯。
  • 我也遇到了这个问题。在有和没有最终 nil 的情况下进行测试 - 结果是相同的。看来您所需要的只是克隆对象。
【解决方案2】:

不要滥用符号。

每次创建符号时,ruby 都会在它的符号表中添加一个条目。符号表是一个全局哈希,永远不会被清空。
这在技术上不是内存泄漏,但它的行为就像一个。符号不会占用太多内存,因此您不必过于偏执,但了解这一点是值得的。

一般准则:如果您确实在代码中键入了符号,那很好(毕竟您只有有限数量的代码),但不要在动态生成或用户输入的字符串上调用 to_sym,因为这样为潜在的不断增加的数字打开了大门

【讨论】:

  • 有点像 Erlang 的原子——简洁。我没有意识到这一点。
  • 在 Ruby 2.2 中,符号将被垃圾回收。
【解决方案3】:

不要这样做:

def method(x)
  x.split( doesn't matter what the args are )
end

或者这个:

def method(x)
  x.gsub( doesn't matter what the args are )
end

Both will permanently leak memory in ruby 1.8.5 and 1.8.6。 (不确定1.8.7,因为我没有尝试过,但我真的希望它是固定的。)解决方法很愚蠢,涉及创建一个局部变量。不用本地的,自己创建一个就可以了……

这就是为什么我非常喜欢 ruby​​ 语言,但不尊重 MRI 的原因

【讨论】:

  • 是的 - 一个众所周知但仍未修复的错误。 rubyforge.org/tracker/… 我很确定它与 MRI 解释器正在执行的优化有关,以避免在范围内没有方法本地变量时创建本地堆栈帧。
  • 在 Ruby 1.8.7 中似乎不再发生这种情况。
【解决方案4】:

当心 C 扩展,它们自己分配大块内存。

例如,当您使用 RMagick 加载图像时,整个位图会在 ruby​​ 进程内加载到内存中。这可能是 30 兆左右,具体取决于图像的大小。
但是,大​​部分内存已由 RMagick 本身分配。 ruby 所知道的只是一个包装对象,它很小(1)。
Ruby 只认为它占用了少量内存,因此它不会打扰运行 GC。实际上它保持在 30 兆。
如果您循环播放 10 张图片,您可能会很快耗尽内存。

首选的解决方案是手动告诉 C 库自己清理内存 - RMagick 有一个破坏!执行此操作的方法。但是,如果您的库没有,您可能需要自己强制运行 GC,即使通常不鼓励这样做。

(1):Ruby C 扩展具有回调,当 ruby​​ 运行时决定释放它们时会运行这些回调,因此内存最终会在某个时候成功释放,但可能还不够快。

【讨论】:

  • 在这种情况下,取消对 blob 的引用并调用 GC.start 可以 释放内存。 RMagic 2.2 应该在内部处理这个问题(但仍然不如 nil + GC 有效)
  • 戴夫,我很好奇你的话。所以,如果你在 Ruby 中有一个对象的大实例,你可以做这样的事情来让 Ruby 释放它?:a = nil GC.start
【解决方案5】:

测量并检测代码的哪些部分正在创建导致内存使用量增加的对象。改进和修改您的代码,然后再次测量。有时,您使用的 gem 或库会占用大量内存并创建大量对象。

有许多工具,例如 busy-administrator,可让您检查对象的内存大小(包括散列和数组中的对象)。

$ gem install busy-administrator

示例 #1:MemorySize.of

require 'busy-administrator'

data = BusyAdministrator::ExampleGenerator.generate_string_with_specified_memory_size(10.mebibytes)

puts BusyAdministrator::MemorySize.of(data)
# => 10 MiB

示例#2:MemoryUtils.profile

代码

require 'busy-administrator'

results = BusyAdministrator::MemoryUtils.profile(gc_enabled: false) do |analyzer|
  BusyAdministrator::ExampleGenerator.generate_string_with_specified_memory_size(10.mebibytes)
end  

BusyAdministrator::Display.debug(results)

输出:

{
    memory_usage:
        {
            before: 12 MiB
            after: 22 MiB
            diff: 10 MiB
        }
    total_time: 0.406452
    gc:
        {
            count: 0
            enabled: false
        }
    specific:
        {
        }
    object_count: 151
    general:
        {
            String: 10 MiB
            Hash: 8 KiB
            BusyAdministrator::MemorySize: 0 Bytes
            Process::Status: 0 Bytes
            IO: 432 Bytes
            Array: 326 KiB
            Proc: 72 Bytes
            RubyVM::Env: 96 Bytes
            Time: 176 Bytes
            Enumerator: 80 Bytes
        }
}

您也可以尝试 ruby-profmemory_profiler。如果您测试和试验不同版本的代码会更好,这样您就可以测量每个版本的内存使用情况和性能。这将允许您检查您的优化是否真的有效。您通常在开发/测试模式下使用这些工具,并在生产中关闭它们。

【讨论】:

    猜你喜欢
    • 2016-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-21
    • 2012-03-21
    相关资源
    最近更新 更多