跳至内容

Ruby 编程/参考/对象/GC

来自维基教科书,开放的书籍,开放的世界

Ruby 使用自动垃圾回收。


调优 GC

[编辑 | 编辑源代码]

MRI 的 GC 是“全标记清除”,并在它用完内存槽时运行(即在添加更多内存之前,它会扫描现有的内存,看看是否可以先释放一些内存——如果没有,它会添加更多内存)。它也会在扩展分配了 GC_MALLOC_LIMIT 字节后被触发。不幸的是,这会导致对所有内存进行遍历,这通常很慢。请参阅 良好的描述

GC 众所周知通常会占用 10% 的 CPU,但如果你有很大的内存负载,它可能会占用更多。

GC 可以在“编译时”(MRI/KRI 的 < 1.9)http://blog.evanweaver.com/articles/2009/04/09/ruby-gc-tuning 进行调优,或者可以使用环境变量(REE、MRI/KRI 的 >= 1.9)进行调优。

一些提示

你可以将编译器变量 GC_MALLOC_LIMIT 设置为一个非常高的值,这会导致你的程序使用更多内存,但更少地遍历它。适用于大型应用,例如 rails。

你可以使用 jruby/rubinius,它们使用更复杂的 GC。

你可以使用“本地”库,它们将值存储起来,这样 Ruby 不必跟踪它们并收集它们。示例:“NArray” gem 和“google_hash” gem。

要关闭它:@GC.disable@

要强制它运行一次:@GC.start@

Ruby(MRI)的 GC 是标记清除,这意味着它是保守的。为了实现这一点,它会遍历堆栈,寻找任何“看起来”像是对现有 Ruby 对象的引用的内存部分,并将其标记为活动。这会导致误报,即使没有剩余对对象的引用。

这个问题在 1.8.x 系列中尤其严重,因为它们没有应用 MBARI 补丁(大多数没有,REE 有)。这是因为,当你使用线程时,它实际上为每个线程分配了堆栈的完整副本,并且当线程运行时,它们的堆栈被复制到“真实”堆栈中,它们可以拾取属于其他线程的幽灵引用,并且因为 1.8 MRI 解释器包含巨大的 switch 语句,这些语句会留下大量未触碰的堆栈内存,因此它可以继续错误地包含对“幽灵”引用的引用。

这意味着,如果你调用 GC.start,它并不*保证*能收集任何东西。

一些提示

  • 如果你从某个方法中调用代码,并*退出*该方法,它可能会更容易收集它。
  • 你可以使用 ensure 块自己进行 GC,例如
 a = SomeClass.new
begin
 ...
ensure
 a.cleanup
end
  • 如果你写了“更多”内存,它可能会清除堆栈中旧的引用。

Jruby 的 GC 调优。

[编辑 | 编辑源代码]

"here":http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/27550 是一个关于如何调优 Jruby 的 GC 的示例。G1GC 理论上是一个“永不暂停”的 GC,但实际上大多数 GC 在速度方面都相当出色。对于长时间运行的应用,你可能需要在服务器模式下运行(--server),以提高性能,尽管启动时间会降低。

据说 Rubinius 也有更好的 GC。

如何避免性能损失

[编辑 | 编辑源代码]

由于 MRI 的 GC 基本上是 O(N),因此当发生 GC 并且你的应用使用大量内存时(而 MRI 几乎从不将内存归还给系统),你会遇到性能损失。解决方法

  1. 通过分配更少的对象来减少内存使用
  2. 在 fork 出来的“子进程”中执行工作,子进程会返回所需的值。子进程会死亡,释放其内存。
  3. 使用 Jruby 等(jruby 有一个出色的 GC,即使在大型应用中也不会降低速度太多)。
  4. 使用允许本地类型的 gem,例如 NArray 或 RubyGoogle Hash。
  5. 使用 REE 代替 1.8.6(因为它包含使 GC 更有效的 MBARI 补丁)。
  6. 使用 1.9.x 代替 1.8.6(因为它使用真正的线程,因此堆栈上的幽灵引用更少,从而使 GC 更有效)。
  7. 将你的应用设置为定期重启(passenger 可以做到这一点)。
  8. 创建多个应用,一个设计为大而慢,其他为灵活(将你所有的 GC 密集型任务运行在大的应用中)。
  9. 自己调用 GC.start,或者与 GC.disable 混合使用
  10. 使用 memprof gem 查看泄漏发生的位置(或者 dike gem 等)。
华夏公益教科书