跳至內容

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 等)。
華夏公益教科書