我使用 Ruby 已经有一段时间了,我发现,对于更大的项目,它会占用相当多的内存。在 Ruby 中减少内存使用的最佳实践是什么?
- 请让每个答案都有一个“最佳实践”,并让社区投票。
我使用 Ruby 已经有一段时间了,我发现,对于更大的项目,它会占用相当多的内存。在 Ruby 中减少内存使用的最佳实践是什么?
在处理大量 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 来处理相同数量的数据......
不要滥用符号。
每次创建符号时,ruby 都会在它的符号表中添加一个条目。符号表是一个永远不会被清空的全局哈希。
这在技术上不是内存泄漏,但它的行为就像一个。符号不会占用太多内存,因此您不必过于偏执,但意识到这一点是值得的。
一般准则:如果您实际上在代码中键入了符号,那很好(毕竟您只有有限数量的代码),但不要在动态生成或用户输入的字符串上调用 to_sym,因为这会打开大门到一个潜在的不断增加的数字
不要这样做:
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
当心 C 扩展,它们自己分配大块内存。
例如,当您使用 RMagick 加载图像时,整个位图会在 ruby 进程内加载到内存中。这可能是 30 兆左右,具体取决于图像的大小。
然而,大部分内存是由 RMagick 自己分配的。ruby 所知道的只是一个包装对象,它很小(1)。
Ruby 只认为它占用了少量内存,因此它不会打扰运行 GC。实际上它保持在 30 兆。
如果您循环播放 10 张图像,您可能会很快耗尽内存。
首选的解决方案是手动告诉 C 库自己清理内存 - RMagick 有一个破坏!执行此操作的方法。但是,如果您的库没有,您可能需要自己强制运行 GC,即使通常不鼓励这样做。
(1): Ruby C 扩展有回调,当 ruby 运行时决定释放它们时会运行,所以内存最终会在某个时候成功释放,只是可能还不够快。
测量并检测代码的哪些部分正在创建导致内存使用量增加的对象。改进和修改您的代码,然后再次测量。有时,您使用的 gem 或库会占用大量内存并创建大量对象。
有许多工具,例如busy-administrator,可让您检查对象的内存大小(包括散列和数组中的那些)。
$ gem install busy-administrator
require 'busy-administrator'
data = BusyAdministrator::ExampleGenerator.generate_string_with_specified_memory_size(10.mebibytes)
puts BusyAdministrator::MemorySize.of(data)
# => 10 MiB
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。如果您测试和试验不同版本的代码会更好,这样您就可以测量每个版本的内存使用情况和性能。这将允许您检查您的优化是否真的有效。您通常在开发/测试模式下使用这些工具,并在生产中关闭它们。