性能優化是使用Ruby開發web應用中一個比較頭痛的問題,因為Ruby本身的執行性能并不高,而且重量級應用Rails使用ruby的方式又是極其消耗內存的。我們知道內存消耗過大對性能有影響,那么內存的消耗是如何影響性能,這就需要對Ruby使用內存的結構和GC的工作方式有比較深入的了解。
內存結構
1.Ruby管理的 棧和堆
在Ruby管理的 棧空間中存放的是 當前運行棧的RVALUE指針和值,而堆空間存放的是,RVALUE值,其中值的大小為固定的40bytes ,其結果是 heap 默認包含24個heap page heap page 默認包括 408個 slot 一個slot 存放一個對象,集RVALUE(Robject RArray..) (按照Ruby默認初始化10_000個slots 計算)。其中Ruby 2.1 中 默認會多初始化一個heap page 也就是25個heap page,并且堆空間的heap page 是按照 eden 和tomb 兩個分區存放的。在運行GC的時候,Ruby會從eden中清空不在使用的heap page,然后移動到 tomb中標記為free。
GC: x: 需要掃描回收的對象數量
y = x/HEAP_OBJ_LIMIT (GC需要掃描的heap page數)
z = 0.8 * (80% of total heap slot count |GC_HEAP_INIT_SLOTS)
2.系統管理的堆
系統管理的堆空間存放的是 數據,即RVALUE中無法存放的的數據,比如一個1K的字符串,RVALUE只能存放引用和元信息和23個字符,剩下真正的數據是存放在系統管理的堆空間的,數據用完后會被GC釋放。
GC觸發條件
1.僅剩余20%的free slots時執行GC (RUBY_GC_HEAP_FREE_SLOTS) 4096
2.mallocate 分配16MB以上內存時執行GC ( RUBY_GC_MALLOC_LIMIT ~ RUBY_GC_MALLOC_LIMIT_MAX 32MBMB區間個字節 具體數字是Ruby根據內存使用情況動態確定的)
問題和優化
Ruby的內存使用方式造就了它在多次GC和malloc之后 堆內存中會出現很多的碎片,這些部分的內存是部分被利用上的,然后就會導致堆內存不足需要繼續分配內存,然后再產生更多碎片一尺類推。
Ruby 2.1之后內存heap分為 eden 和 tomb 兩個部分,分別存放新生對象和死去的對象,當你創建對象的時候,ruby會從eden space 分配內存,如果eden中的內存不足的話,再從tomb空間中分配內存使用。這么做的原因有兩個,其一是通過重復使用 heap space 來減少內存空間的碎片。其二是方便解釋器釋放整塊內存空間。
那么在真正的使用場景下,Ruby僅僅釋放了tomb空間10%的內存,因為ruby解釋器程序具有馬太效應,使用過多對象的應用,在未來還會使用更多的對象,也就是更多的heap space所以,它通過保留80%多的tomb內存空間。這樣未來要使用更多的內存空間時,不需要再次分配內存(分配內存消耗資源)。
優化內存的使用主要就是兩點,一是添加內存分配的參數,二是調整GC觸發的參數。下面的幾個Ruby參數就是和內存使用優化相關的參數:
RUBY_GC_HEAP_INIT_SLOTS: Ruby初始化slots數量,數量越大后續malloc次數越小
RUBY_GC_HEAP_GROWTH_FACTOR:內存分配增長因子
RUBY_GC_MALLOC_LIMIT: GC觸發條件之一, 分配內存超過限制后運行GC
RUBY_GC_HEAP_FREE_SLOTS:對堆空間free slot少于該參數是 觸發GC
最后這些參數修改的要點是,需要根據你現有應用的內存使用情況來設置,比如像Rails這樣的應用可以在初始化的時候就需要分配很多的內存,那么就可以一開始吧初始化的slots數量調高一些,這樣在應用啟動時能夠減少malloc的數量。