內存優化總結:ptmalloc、tcmalloc和jemalloc

概述

需求

系統的物理內存是有限的,而對內存的需求是變化的, 程序的動態性越強,內存管理就越重要,選擇合適的內存管理算法會帶來明顯的性能提高。
好比nginx, 它在每一個鏈接accept後會malloc一塊內存,做爲整個鏈接生命週期內的內存池。 當HTTP請求到達的時候,又會malloc一塊當前請求階段的內存池, 所以對malloc的分配速度有必定的依賴關係。(而apache的內存池是有父子關係的,請求階段的內存池會和鏈接階段的使用相同的分配器,若是鏈接內存池釋放則請求階段的子內存池也會自動釋放)。html

目標

內存管理能夠分爲三個層次,自底向上分別是:mysql

  • 操做系統內核的內存管理
  • glibc層使用系統調用維護的內存管理算法
  • 應用程序從glibc動態分配內存後,根據應用程序自己的程序特性進行優化, 好比使用引用計數std::shared_ptr,apache的內存池方式等等。
    固然應用程序也能夠直接使用系統調用從內核分配內存,本身根據程序特性來維護內存,可是會大大增長開發成本。

本文主要介紹了glibc malloc的實現,及其替代品nginx

一個優秀的通用內存分配器應具備如下特性:git

  • 額外的空間損耗盡可能少
  • 分配速度儘量快
  • 儘可能避免內存碎片
  • 緩存本地化友好
  • 通用性,兼容性,可移植性,易調試

現狀

目前大部分服務端程序使用glibc提供的malloc/free系列函數,而glibc使用的ptmalloc2在性能上遠遠弱後於google的tcmalloc和facebook的jemalloc。 並且後二者只須要使用LD_PRELOAD環境變量啓動程序便可,甚至並不須要從新編譯。github

glibc ptmalloc2

ptmalloc2便是咱們當前使用的glibc malloc版本。算法

ptmalloc原理

系統調用接口

x86進程地址空間
上圖是 x86_64 下 Linux 進程的默認地址空間, 對 heap 的操做, 操做系統提供了brk()系統調用,設置了Heap的上邊界; 對 mmap 映射區域的操做,操做系 統 供了 mmap()和 munmap()函數。
由於系統調用的代價很高,不可能每次申請內存都從內核分配空間,尤爲是對於小內存分配。 並且由於mmap的區域容易被munmap釋放,因此通常大內存採用mmap(),小內存使用brk()。sql

多線程支持

  • Ptmalloc2有一個主分配區(main arena), 有多個非主分配區。 非主分配區只能使用mmap向操做系統批發申請HEAP_MAX_SIZE(64位系統爲64MB)大小的虛擬內存。 當某個線程調用malloc的時候,會先查看線程私有變量中是否已經存在一個分配區,若是存在則嘗試加鎖,若是加鎖失敗則遍歷arena鏈表試圖獲取一個沒加鎖的arena, 若是依然獲取不到則建立一個新的非主分配區。
  • free()的時候也要獲取鎖。分配小塊內存容易產生碎片,ptmalloc在整理合並的時候也要對arena作加鎖操做。在線程多的時候,鎖的開銷就會增大。

ptmalloc內存管理

  • 用戶請求分配的內存在ptmalloc中使用chunk表示, 每一個chunk至少須要8個字節額外的開銷。 用戶free掉的內存不會立刻歸還操做系統,ptmalloc會統一管理heap和mmap區域的空閒chunk,避免了頻繁的系統調用。
  • ptmalloc 將類似大小的 chunk 用雙向鏈表連接起來, 這樣的一個鏈表被稱爲一個 bin。Ptmalloc 一共 維護了 128 個 bin,並使用一個數組來存儲這些 bin(以下圖所示)。
    ptmalloc結構圖
    數組中的第一個爲 unsorted bin, 數組中從 2 開始編號的前 64 個 bin 稱爲 small bins, 同一個small bin中的chunk具備相同的大小。small bins後面的bin被稱做large bins。chrome

  • 當free一個chunk並放入bin的時候, ptmalloc 還會檢查它先後的 chunk 是否也是空閒的, 若是是的話,ptmalloc會首先把它們合併爲一個大的 chunk, 而後將合併後的 chunk 放到 unstored bin 中。 另外ptmalloc 爲了提升分配的速度,會把一些小的(不大於64B) chunk先放到一個叫作 fast bins 的容器內。apache

  • 在fast bins和bins都不能知足需求後,ptmalloc會設法在一個叫作top chunk的空間分配內存。 對於非主分配區會預先經過mmap分配一大塊內存做爲top chunk, 當bins和fast bins都不能知足分配須要的時候, ptmalloc會設法在top chunk中分出一塊內存給用戶, 若是top chunk自己不夠大, 分配程序會從新mmap分配一塊內存chunk, 並將 top chunk 遷移到新的chunk上,並用單鏈表連接起來。若是free()的chunk剛好 與 top chunk 相鄰,那麼這兩個 chunk 就會合併成新的 top chunk,若是top chunk大小大於某個閾值才還給操做系統。主分配區相似,不過經過sbrk()分配和調整top chunk的大小,只有heap頂部連續內存空閒超過閾值的時候才能回收內存。
  • 須要分配的 chunk 足夠大,並且 fast bins 和 bins 都不能知足要求,甚至 top chunk 自己也不能知足分配需求時,ptmalloc 會使用 mmap 來直接使用內存映射來將頁映射到進程空間。

ptmalloc分配流程

ptmalloc分配流程

ptmalloc的缺陷

  • 後分配的內存先釋放,由於 ptmalloc 收縮內存是從 top chunk 開始,若是與 top chunk 相鄰的 chunk 不能釋放, top chunk 如下的 chunk 都沒法釋放。
  • 多線程鎖開銷大, 須要避免多線程頻繁分配釋放。
  • 內存從thread的areana中分配, 內存不能從一個arena移動到另外一個arena, 就是說若是多線程使用內存不均衡,容易致使內存的浪費。 好比說線程1使用了300M內存,完成任務後glibc沒有釋放給操做系統,線程2開始建立了一個新的arena, 可是線程1的300M卻不能用了。
  • 每一個chunk至少8字節的開銷很大
  • 不按期分配長生命週期的內存容易形成內存碎片,不利於回收。 64位系統最好分配32M以上內存,這是使用mmap的閾值。

tcmalloc

tcmalloc是Google開源的一個內存管理庫, 做爲glibc malloc的替代品。目前已經在chrome、safari等知名軟件中運用。
根據官方測試報告,ptmalloc在一臺2.8GHz的P4機器上(對於小對象)執行一次malloc及free大約須要300納秒。而TCMalloc的版本一樣的操做大約只須要50納秒。數組

小對象分配

  • tcmalloc爲每一個線程分配了一個線程本地ThreadCache,小內存從ThreadCache分配,此外還有個中央堆(CentralCache),ThreadCache不夠用的時候,會從CentralCache中獲取空間放到ThreadCache中。
  • 小對象(<=32K)從ThreadCache分配,大對象從CentralCache分配。大對象分配的空間都是4k頁面對齊的,多個pages也能切割成多個小對象劃分到ThreadCache中。
    tcmalloc小對象類型鏈表
    小對象有將近170個不一樣的大小分類(class),每一個class有個該大小內存塊的FreeList單鏈表,分配的時候先找到best fit的class,而後無鎖的獲取該鏈表首元素返回。若是鏈表中無空間了,則到CentralCache中劃分幾個頁面並切割成該class的大小,放入鏈表中。

CentralCache分配管理

  • 大對象(>32K)先4k對齊後,從CentralCache中分配。 CentralCache維護的PageHeap以下圖所示, 數組中第256個元素是全部大於255個頁面都掛到該鏈表中。
    tcmalloc-pageheap
  • 當best fit的頁面鏈表中沒有空閒空間時,則一直往更大的頁面空間則,若是全部256個鏈表遍歷後依然沒有成功分配。 則使用sbrk, mmap, /dev/mem從系統中分配。
  • tcmalloc PageHeap管理的連續的頁面被稱爲span.
    若是span未分配, 則span是PageHeap中的一個鏈表元素
    若是span已經分配,它多是返回給應用程序的大對象, 或者已經被切割成多小對象,該小對象的size-class會被記錄在span中
  • 在32位系統中,使用一箇中央數組(central array)映射了頁面和span對應關係, 數組索引號是頁面號,數組元素是頁面所在的span。 在64位系統中,使用一個3-level radix tree記錄了該映射關係。

回收

  • 當一個object free的時候,會根據地址對齊計算所在的頁面號,而後經過central array找到對應的span。
  • 若是是小對象,span會告訴咱們他的size class,而後把該對象插入當前線程的ThreadCache中。若是此時ThreadCache超過一個預算的值(默認2MB),則會使用垃圾回收機制把未使用的object從ThreadCache移動到CentralCache的central free lists中。
  • 若是是大對象,span會告訴咱們對象鎖在的頁面號範圍。 假設這個範圍是[p,q], 先查找頁面p-1和q+1所在的span,若是這些臨近的span也是free的,則合併到[p,q]所在的span, 而後把這個span回收到PageHeap中。
  • CentralCache的central free lists相似ThreadCache的FreeList,不過它增長了一級結構,先根據size-class關聯到spans的集合, 而後是對應span的object鏈表。若是span的鏈表中全部object已經free, 則span回收到PageHeap中。

tcmalloc的改進

  • ThreadCache會階段性的回收內存到CentralCache裏。 解決了ptmalloc2中arena之間不能遷移的問題。
  • Tcmalloc佔用更少的額外空間。例如,分配N個8字節對象可能要使用大約8N * 1.01字節的空間。即,多用百分之一的空間。Ptmalloc2使用最少8字節描述一個chunk。
  • 更快。小對象幾乎無鎖, >32KB的對象從CentralCache中分配使用自旋鎖。 而且>32KB對象都是頁面對齊分配,多線程的時候應儘可能避免頻繁分配,不然也會形成自旋鎖的競爭和頁面對齊形成的浪費。

性能對比

官方測試

測試環境是2.4GHz dual Xeon,開啓超線程,redhat9,glibc-2.3.2, 每一個線程測試100萬個操做。
tcmalloc測試1
上圖中能夠看到尤爲是對於小內存的分配, tcmalloc有很是明顯性能優點。
tcmalloc測試2
上圖能夠看到隨着線程數的增長,tcmalloc性能上也有明顯的優點,而且相對平穩。

github mysql優化

github使用tcmalloc後,mysql性能提高30%

Jemalloc

jemalloc是facebook推出的, 最先的時候是freebsd的libc malloc實現。 目前在firefox、facebook服務器各類組件中大量使用。

jemalloc原理

  • 與tcmalloc相似,每一個線程一樣在<32KB的時候無鎖使用線程本地cache。
  • Jemalloc在64bits系統上使用下面的size-class分類:
    Small: [8], [16, 32, 48, …, 128], [192, 256, 320, …, 512], [768, 1024, 1280, …, 3840]
    Large: [4 KiB, 8 KiB, 12 KiB, …, 4072 KiB]
    Huge: [4 MiB, 8 MiB, 12 MiB, …]
  • small/large對象查找metadata須要常量時間, huge對象經過全局紅黑樹在對數時間內查找。
  • 虛擬內存被邏輯上分割成chunks(默認是4MB,1024個4k頁),應用線程經過round-robin算法在第一次malloc的時候分配arena, 每一個arena都是相互獨立的,維護本身的chunks, chunk切割pages到small/large對象。free()的內存老是返回到所屬的arena中,而不論是哪一個線程調用free()。

jemalloc Arena Chunk layout
上圖能夠看到每一個arena管理的arena chunk結構, 開始的header主要是維護了一個page map(1024個頁面關聯的對象狀態), header下方就是它的頁面空間。 Small對象被分到一塊兒, metadata信息存放在起始位置。 large chunk相互獨立,它的metadata信息存放在chunk header map中。

  • 經過arena分配的時候須要對arena bin(每一個small size-class一個,細粒度)加鎖,或arena自己加鎖。
    而且線程cache對象也會經過垃圾回收指數退讓算法返回到arena中。
    jemalloc Arena and thread cache layout

jemalloc的優化

  • Jmalloc小對象也根據size-class,可是它使用了低地址優先的策略,來下降內存碎片化。
  • Jemalloc大概須要2%的額外開銷。(tcmalloc 1%, ptmalloc最少8B)
  • Jemalloc和tcmalloc相似的線程本地緩存,避免鎖的競爭
  • 相對未使用的頁面,優先使用dirty page,提高緩存命中。

性能對比

官方測試

jemalloc官方測試報告
上圖是服務器吞吐量分別用6個malloc實現的對比數據,能夠看到tcmalloc和jemalloc最好(facebook在2011年的測試結果,tcmalloc這裏版本較舊)。

4.3.2 mysql優化
測試環境:2x Intel E5/2.2Ghz with 8 real cores per socket,16 real cores, 開啓hyper-threading, 總共32個vcpu。 16個table,每一個5M row。
OLTP_RO測試包含5個select查詢:select_ranges, select_order_ranges, select_distinct_ranges, select_sum_ranges,
jemalloc mysql性能測試

能夠看到在多核心或者多線程的場景下, jemalloc和tcmalloc帶來的tps增長很是明顯。

參考資料

glibc內存管理ptmalloc源代碼分析
Inside jemalloc
tcmalloc淺析
tcmalloc官方文檔
Scalable memory allocation using jemalloc
mysql-performance-impact-of-memory-allocators-part-2
ptmalloc,tcmalloc和jemalloc內存分配策略研究
Tick Tock, malloc Needs a Clock

總結

在多線程環境使用tcmalloc和jemalloc效果很是明顯。
當線程數量固定,不會頻繁建立退出的時候, 可使用jemalloc;反之使用tcmalloc多是更好的選擇。

 

文章轉載自:http://www.cnhalo.net/2016/06/13/memory-optimize/

相關文章
相關標籤/搜索