內存分配器 (Memory Allocator)

對於大多數開發人員而言,系統的內存分配就是一個黑盒子,就是幾個API的調用。有你就給我,沒有我就想別的辦法。css

來UC前,我就是這樣以爲的。實際深刻進去時,才發現這個領域裏也是百家爭鳴。很是熱鬧。有操做系統層面的內存分配器(Memory Allocator)。有應用程序層面的,有爲實時系統設計的,有爲服務程序設計的。html

但他們的目的倒是同樣的。平衡內存分配的性能和提升內存使用的效率。web


從瀏覽器開發的角度看,手機內存的增加速度相對於網頁內容的增加仍然僅僅是溫飽水平,像Android自己就是用內存大戶。另外一個Low Memory Killer, 必定要優化內存佔用。總體上對策就是兩點:一是能不用就不用,代碼裏可能隱藏着很是多沒必要要內存分配。特別留意那些中間量。二是能少用就少用,特別避免頻繁分配。因爲那樣僅僅會添加內存碎片,到了極端時即便仍有內存可用,也分配不出來了。還有一個選項: 換個內存分配器
這樣一是假設內存分配器優良就可以緩解內存碎片,也可以在出現OOM時控制程序的行爲,崩與不崩、崩在哪裏就可以本身控制了。算法


近期因爲工做緣由。涉及到了小內存分配器,因此作了一些粗淺的學習。沒有完整的閱讀代碼。也沒有進行透徹的測試,僅僅是寫個總結以及相關的文檔放在這裏,備查。數組


內存分配的現實問題


首先一般使用的內存分配器,即malloc/free函數並不是系統提供的,而是C標準庫提供的。也被稱爲動態內存分配器。分配器從操做系統拿內存(虛擬內存)時是以頁爲單位(通常是4KB,調用sbrk或mmap), 而後再自行管理。瀏覽器


上面也提到了,內存分配器面對的是兩個核心問題: 效能和性能(或稱爲吞吐量Throughoutput)。 前者保證隨時有內存可用,後者保證服務時間短、不拖後腿。數據結構


對於一個系統進程而言,面對OOM(Out Of Memory)問題,排除程序使用內存的Bug外,會有兩個緣由:
  1.系統真的沒有內存可用了。
多線程


  2.內存分配浪費了大量空間。儘管有大量零散的可用空間,卻沒法合併提供出來使用。 前者纔是真正的OOM, 後者就是內存碎片(Fragmentation)問題了。
ide



libc裏的malloc遇到分配失敗時,默認會abort掉進程。也就是崩掉(CRASH)了。函數

假設系統支持mallopt就有機會改變這個行爲。惋惜Android尚未支持。

瀏覽器在載入、解析、渲染頁面的時候,會分配大量的小對象,看張圖就明確了:

    

上圖中模軸爲對象大小,縱軸則爲申請分配的次數。假設內存都以頁爲單位申請,就簡單,也就不需要分配器。就是那些小對象,佔用很少,使用頻繁,很是easy形成頁內沒法再繼續使用的碎片(Internal Fragmentation)。


對於性能,內存分配是次於I/O的一個瓶徑。

儘管絕大多數狀況下都相安無事。但內存分配器有一個重要的指標,即上限(bounded limits)。儘管平均值看起很是好,但一旦遇到最壞的狀況(wrost case)時。能不能保證性能?特別是多線程下,內存分配、釋放的性能常常受到加鎖的影響。有些分配器(如ptmalloc)過於考慮性能,而沒法使線程間的內存共享,各自佔去一塊,反而減小了內存使用的效率。


這些問題一直存在。不一樣的人針對不一樣的場景設計出了不一樣的分配器算法(DSA, Dynamic Storage Algorithms, 是以應用的角度來看的)。而且差點兒每個都說本身比別人強。比方:
   1. dlmalloc/ptmalloc/ptmallocX C標準庫提供的分配器, 也是應用程序默認使用的malloc/free等函數。


   2. tcmalloc 出自Google, WebKit/Chrome中應用。


   3. bmalloc 畢竟Chrome和WebKit越走越遠。因此Apple在WebKit最新代碼(2014-04)裏提供了新的分配器,號稱遠遠超過 TCMalloc, 至少是在性能上。
   4. jemalloc 本來是爲FreeBSD開發的,後來Firefox瀏覽器和FaceBook的服務端都加以應用,它自身也在這些應用中獲得了大幅提高。


   5. Hoard 一個專爲多線程優化的分配器, 做者是大學教授。有一些獨特的技術。Mac OS X中的malloc就有參考事實上現進行優化。

*WebKit另外專爲Render Object提供了一個所謂的Plain Old Data Areana的類,也算是一個Memory Pool的實現(PODIntervalTree, PODArena)。



核心思想和算法


分配器這麼多,其核心思想相似,僅僅是差在算法和metadata存儲上。附13提供的論文中有比較全面的總結,可以翻看一下。


內存分配器的核心思想歸納起來三條:


1. 基本功能:首先將內存區(Memory Pool)以最小單位(chunk)定義出來。而後區分對象大小分別管理內存。小內存分紅若干類(size class),專門用來分配固定大小的內存塊,並用一個表管理起來。減小內部碎片(internal fragmentation)。大內存則以頁爲單位管理, 配合小對象所在的頁,減小碎片。設計一個好的存儲方案。即metadata的存儲。減小對內存的佔用。

同一時候優化內存信息的存儲,以使對每個size class或大內存區域的訪問的性能最優且有上限(bounded limits)。


比方dlmalloc定義的是一個個bins(同size class)來存儲不一樣大小的內存塊:

     
2. 回收及預測功能: 當釋放內存時。要可以合併小內存爲大內存,依據一些條件,該保留的就保留起來,在下次使用時可以高速的響應。不需要保留時。則釋放回系統。避免長期佔用。


3. 優化多線程下性能問題:針對多線程環境下,每個線程可以獨立佔有一段內存區間。被稱爲TLS(Thread Local Storage)。這樣線程內操做時可以不加鎖,提升性能。

下圖是MSDN上貼出的關於TLS的原理圖,可以參考:

        


*另外測試工具也是不可缺乏,比方tcmalloc的heap profile, jemalloc則結合valgrind。FireFox在移植jemalloc到Android時。特別關掉了TLS,想必是考慮到它對於線程單一應用的反作用。


上面這些思路對於各個分配器而言基本是一致,但詳細怎樣組織size classes, 假設以一個固定步長,必將造成一個巨大且效率低下的表。緣由參考第一張圖就明確了。

很是多年前,就有專門的論文對此作了評定(連接)。另外還有怎樣定位內存塊? 怎樣解決多線程下的false cache line問題? 不一樣的分配器使用了不一樣的算法和數據結構來實現。它們所使用的算法統稱爲DSA, Dynamic Storage Algorithms。


詳細的算法實現可以在如下的參考列表中找到相應的文檔, 也可以先看附16。文中分別對DSA Algorithms和DSA Operational Model作了描寫敘述,歸納的很是好。會有一個總體的印象。做者將DSA算法分爲五類:

  1. Sequential Fit

     是基於一個單向或雙向鏈表管理各個blocks的基礎算法。因爲和blocks的個數有關。性能比較差。這一類算法包含Fast-Fit, First-Fit, Next-Fit, and Worst-Fit。

  2. Segregated Free List (離散式空暇列表) 

     使用一個數組,每個元素是存儲特定大小內存塊的鏈表。它們所表明的大小並不是連續的,因此稱爲離散。經典的dlmalloc使用的就是這個算法。數據元素,參照上面的圖就可以理解了。TLSF算法則是基於此進行了改進。

  3. Buddy System

    這是由一代大師Donald Knuth提出。興許產生不少的改進版本號。最大的做用是解決外部碎片(external fragmentation), 詳細的算法。參考這篇(淺析Linux內核內存管理之Buddy System)。

  4. Indexed Fit

   以某種數據結構爲每個block創建索引,以求可以高速存取。

通常以一個二叉樹結構實現。比方使用Balanced Tree的Best Fit allocator, 以及基於Cartesian tree 的Stephenson Fast-Fit allocator。這類算法的性能比較高,也比較穩定。

  5. Bitmap Fit

   這類算法僅僅是索引方法不一樣,使用以位圖式字節表示存儲單元的狀態。它的優勢是使用一小塊連續的內存,響應性能更好。

Half-Fit就屬於這類算法。

隨着技術演進。現在主流的allocators, 基本上都是綜合運用了兩類以上的算法。


另一些基礎算法也是相似的。比方以二叉樹組織列表的算法,也就是in-place, 笛卡爾樹 和red-block的差別。在線程上。則因爲實現的不一樣,會致使內存佔用的差別。比方jemalloc在釋放時,並不需要在原來的分配線程運行釋放。僅僅是被放回到分配線程的free list中去。ptmalloc則必須回到分配線程裏運行釋放,性能就相對弱一些。

tcmalloc則設計了算法。讓一個線程可以從它的鄰居那裏偷一些空間來(這個過程稱爲transfer cache)。這樣可以有效地利用線程間的內存。


優劣勢對照

ptmalloc 劣勢:多線程下的性能及內存佔用(線程間內存沒法共享),而且內存用於存儲metadata開銷較大。在小內存分配上浪費比較多。

優點:算是標準實現。

tcmalloc 劣勢:因爲算法的設計,佔用的內存較大。優點:多線程下的性能。參考附6。

jemalloc 優點: 內存碎片率低。多核下性能較tcmalloc更好。參考附17。


時間有限,沒有再深刻研究,後面有空再補充一下。在實際應用中,仍是有一些參數可以調整的,前提是要熟悉事實上現,特別是性能評估的方法。


轉載請註明出處: http://blog.csdn.net/horkychen


參考


這是我列的最長的參考清單了。前人的確已經作了很是多的研究,我對當中內容僅僅是泛讀,並不是所有內容都相關,僅僅是以爲有些內容可以相互應證就也列進來了。


1. jemalloc關於使用red-block tree的反思 [連接]
  文章公佈於2008年。做者在2009年將其應用於FaceBook時。則是進行了算法上優化。
2. 2011年jemalloc做者在FaceBook應用jemalloc後撰文介紹了jemalloc的核心算法及在Facebook上應用效果。

[連接] [早期的論文,有不少其餘的細節]
3. Android碎片化的度量 經過改造ROM作的實驗。
4. Hoard Offical [連接]
5. Mac OS上malloc是怎麼工做的[連接]
6. 關於WebKit應用tcmalloc的對照[連接]
7. How tcmalloc works[連接] [中文翻譯]
8. TCMalloc源碼分析,很是不錯資料。

做者的站點還有其餘乾貨值得一讀。[連接]
9. dlmalloc早期的技術文檔,講述了其核心算法。[連接]
10. ptmalloc源碼分析,講的很是系統。很是值得一讀。[CSDN下載連接]
11. 介紹jemalloc的資料《更好的內存管理-jemalloc》[連接]
12. 替換系統malloc的四種方法 [連接]
13. 介紹針對實時系統進行優化的內存分配算法TLSF,當中對動態分配算法(DSA)作了總結。[連接]
14. 維基百科上關於Thread Local Storage的說明, 或許你能感覺到技術的相通性。[連接]
15. 針對實時系統進行各類分配算法的對照,可以結合13一塊兒看。

[連接]
16. ptmalloc,tcmalloc和jemalloc內存分配策略研究。[連接]
17. Firefox3使用jemalloc後的總結,可以看到Firefox優化的思路。[連接] [Firefox使用的源碼]
18. Chromimum Project: Out of memory handling, 裏面有不錯的觀點。 [連接]

相關文章
相關標籤/搜索