Ruby 程式設計/參考/物件/GC
Ruby 使用自動垃圾回收。
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
- 如果你寫了“更多”記憶體,它可能會清除堆疊中舊的引用。
"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 幾乎從不將記憶體歸還給系統),你會遇到效能損失。解決方法
- 透過分配更少的物件來減少記憶體使用
- 在 fork 出來的“子程序”中執行工作,子程序會返回所需的值。子程序會死亡,釋放其記憶體。
- 使用 Jruby 等(jruby 有一個出色的 GC,即使在大型應用中也不會降低速度太多)。
- 使用允許本地型別的 gem,例如 NArray 或 RubyGoogle Hash。
- 使用 REE 代替 1.8.6(因為它包含使 GC 更有效的 MBARI 補丁)。
- 使用 1.9.x 代替 1.8.6(因為它使用真正的執行緒,因此堆疊上的幽靈引用更少,從而使 GC 更有效)。
- 將你的應用設定為定期重啟(passenger 可以做到這一點)。
- 建立多個應用,一個設計為大而慢,其他為靈活(將你所有的 GC 密集型任務執行在大的應用中)。
- 自己呼叫 GC.start,或者與 GC.disable 混合使用
- 使用 memprof gem 檢視洩漏發生的位置(或者 dike gem 等)。