24

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

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

5 回答 5

27

在处理大量 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 来处理相同数量的数据......

于 2010-09-21T22:32:05.493 回答
22

不要滥用符号。

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

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

于 2008-10-08T05:12:22.860 回答
15

不要这样做:

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

两者都会在 ruby​​ 1.8.5 和 1.8.6 中永久泄漏内存。(不确定1.8.7,因为我没有尝试过,但我真的希望它是固定的。)解决方法很愚蠢,涉及创建一个局部变量。您不必使用本地,只需创建一个...

像这样的事情就是为什么我非常喜欢 ruby​​ 语言,但不尊重 MRI

于 2008-10-08T05:18:09.150 回答
9

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

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

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

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

于 2008-10-08T05:09:20.633 回答
0

测量并检测代码的哪些部分正在创建导致内存使用量增加的对象。改进和修改您的代码,然后再次测量。有时,您使用的 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 ​​-prof和memory_profiler。如果您测试和试验不同版本的代码会更好,这样您就可以测量每个版本的内存使用情况和性能。这将允许您检查您的优化是否真的有效。您通常在开发/测试模式下使用这些工具,并在生产中关闭它们。

于 2016-09-25T15:46:25.660 回答