1. 程式人生 > >幾種常用記憶體管理底層介紹

幾種常用記憶體管理底層介紹

需求

系統的實體記憶體是有限的,而對記憶體的需求是變化的, 程式的動態性越強,記憶體管理就越重要,選擇合適的記憶體管理演算法會帶來明顯的效能提升。
比如nginx, 它在每個連線accept後會malloc一塊記憶體,作為整個連線生命週期內的記憶體池。 當HTTP請求到達的時候,又會malloc一塊當前請求階段的記憶體池, 因此對malloc的分配速度有一定的依賴關係。(而apache的記憶體池是有父子關係的,請求階段的記憶體池會和連線階段的使用相同的分配器,如果連線記憶體池釋放則請求階段的子記憶體池也會自動釋放)。

目標

記憶體管理可以分為三個層次,自底向上分別是:

  • 作業系統核心的記憶體管理
  • glibc層使用系統呼叫維護的記憶體管理演算法
  • 應用程式從glibc動態分配記憶體後,根據應用程式本身的程式特性進行優化, 比如使用引用計數std::shared_ptr,apache的記憶體池方式等等。
    當然應用程式也可以直接使用系統呼叫從核心分配記憶體,自己根據程式特性來維護記憶體,但是會大大增加開發成本。

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

一個優秀的通用記憶體分配器應具有以下特性:

  • 額外的空間損耗盡量少
  • 分配速度儘可能快
  • 儘量避免記憶體碎片
  • 快取本地化友好
  • 通用性,相容性,可移植性,易除錯

現狀

目前大部分服務端程式使用glibc提供的malloc/free系列函式,而glibc使用的ptmalloc2在效能上遠遠弱後於google的tcmalloc和facebook的jemalloc。 而且後兩者只需要使用LD_PRELOAD環境變數啟動程式即可,甚至並不需要重新編譯。

glibc ptmalloc2

ptmalloc2即是我們當前使用的glibc malloc版本。

ptmalloc原理

系統呼叫介面

x86程序地址空間
上圖是 x86_64 下 Linux 程序的預設地址空間, 對 heap 的操作, 作業系統提供了brk()系統呼叫,設定了Heap的上邊界; 對 mmap 對映區域的操作,操作系 統 供了 mmap()和 munmap()函式。
因為系統呼叫的代價很高,不可能每次申請記憶體都從核心分配空間,尤其是對於小記憶體分配。 而且因為mmap的區域容易被munmap釋放,所以一般大記憶體採用mmap(),小記憶體使用brk()。

多執行緒支援

  • 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。

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

  • 在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優化

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增加非常明顯。

參考資料

總結

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

相關推薦

常用記憶體管理底層介紹

需求 系統的實體記憶體是有限的,而對記憶體的需求是變化的, 程式的動態性越強,記憶體管理就越重要,選擇合適的記憶體管理演算法會帶來明顯的效能提升。 比如nginx, 它在每個連線accept後會malloc一塊記憶體,作為整個連線生命週期內的記憶體池。 當HTTP請求

JAVA中常用的RPC框架介紹

github 不同的 target int https love num 分布 有一個 RPC是遠程過程調用的簡稱,廣泛應用在大規模分布式應用中,作用是有助於系統的垂直拆分,使系統更易拓展。Java中的RPC框架比較多,各有特色,廣泛使用的有RMI、Hessian、Du

【面試必讀(程式設計基礎)】常用的設計模式介紹

http://blog.csdn.net/xsl1990/article/details/16359289 每個設計模式詳細介紹請看 http://blog.jobbole.com/tag/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/

[轉載]Scikit-learn介紹常用的特徵選擇方法

#### [轉載]原文地址:http://dataunion.org/14072.html 特徵選擇(排序)對於資料科學家、機器學習從業者來說非常重要。好的特徵選擇能夠提升模型的效能,更能幫助我們理解資料的特點、底層結構,這對進一步改善模型、演算法都有著重要作用。 特徵選擇主要有兩個功能: 減少特

[轉載]Scikit-learn介紹常用的特征選擇方法

valid 好的 多重 variable 統計學 助理 相關性 sele 不出 #### [轉載]原文地址:http://dataunion.org/14072.html 特征選擇(排序)對於數據科學家、機器學習從業者來說非常重要。好的特征選擇能夠提升模型的性能,更能幫助我

iOS效能檢測之Instrunments - 常用工具簡單介紹

Instrunments: 沒錯,就是這貨,很多人平時開發可能不一定會用到這個,但我要說的是,學會使用它,會讓你加分不少哦 先來一張一覽圖: 兩種方式都行。 2、今天主要介紹一下我常用的3種:Core Animation 、Leaks、Time Profiler

記憶體池——第一章 常用記憶體池技術

#define EXTRA_BUFFER_SIZE        64 namespace easy {     template<class _Type,class _Alloc >     class EasyRingbuffer      {   

結合Scikit-learn介紹常用的特徵選擇方法

特徵選擇(排序)對於資料科學家、機器學習從業者來說非常重要。好的特徵選擇能夠提升模型的效能,更能幫助我們理解資料的特點、底層結構,這對進一步改善模型、演算法都有著重要作用。特徵選擇主要有兩個功能:減少特徵數量、降維,使模型泛化能力更強,減少過擬合增強對特徵和特徵值之間的理解拿

[轉]乾貨:結合Scikit-learn介紹常用的特徵選擇方法

特徵選擇(排序)對於資料科學家、機器學習從業者來說非常重要。好的特徵選擇能夠提升模型的效能,更能幫助我們理解資料的特點、底層結構,這對進一步改善模型、演算法都有著重要作用。 特徵選擇主要有兩個功能: 減少特徵數量、降維,使模型泛化能力更強,減少過擬合 增強對特徵和特徵值之間的理解

如何避免記憶體洩漏、溢位的常用方法

儘早釋放無用物件的引用。 好的辦法是使用臨時變數的時候,讓引用變數在退出活動域後自動設定為null,暗示垃圾收集器來收集該物件,防止發生記憶體洩露。 程式進行字串處理時,儘量避免使用String,而應使用StringBuffer。 因為每一個String物

java 中常用數據結構

初學 ble log app 使用 blog list 好的 sort Java中有幾種常用的數據結構,主要分為Collection和map兩個主要接口(接口只提供方法,並不提供實現),而程序中最終使用的數據結構是繼承自這些接口的數據結構類。 一、幾個常用類的區別 1.

常用的空格

idt 而且 根據 筆記本 p s 時也 引用 其中 等等 今天上午把在家寫好的代碼下載到另一個筆記本上,一調試發現,出現的結果和之前不一樣。我用代碼寫好的等腰三角形,在家調試完成沒有任何問 題。在這臺筆記本上顯示成了直角三角形。仔細檢查了下,代碼並沒有發現什麽問

C#中常用的集合的用法

col div tex -c 組成 相同 列表 對象 count 集合:將一推數據類型相同的數據放入到一個容器內,該容器就是數組:內存中開辟的一連串空間。 非泛型集合 ArrayList集合: ArrayList是基於數組實現的,是一個動態數組,其容量能自動 增

SSH免登陸ESXI讓操作更便捷安全(常用工具)

esxi vmware ssh工具 免登陸 授權訪問通常在企業環境中,管理員很少去機房在物理機面前進行操作,大部分管理員喜歡遠程管理主機,這樣既方便又高效。在windows上我們使用遠程桌面,在Linux上則使用SSH來連接。ESXi就是一個以Linux為核心改寫的操作系統,因此ESXi中也保留了SSH

JAVA獲取文件的常用方式

ada col epo term 打印 core book port nbsp 1、user.dir System.out.println(System.getProperty("user.dir")); 此方獲取的是運行的路 比如 1、 2、如果在eclipse上運

Python中time模塊和datetime模塊的常用操作以及常用時間格式間的轉換

pyrhon time datatime 幾種常用時間格式的轉換 最常見以及常用的幾種時間格式 1、時間戳(timestamp),時間戳表示的是從1970年1月1日00:00:00開始按秒計算的偏移量。 2、時間元組(struct_time),共有九個元素組。 3、格式化時間(fo

總結常用的安全算法

多人 很難 .html 如果 tab ble www. 解密 是我 摘要算法 對稱加密算法 非對稱加密算法 數字簽名 數字證書 web安全系列目錄 總結幾種常見web攻擊手段極其防禦方式 總結幾種常見的安全算法 數字摘要 實現 將任意長度的明文通過單向hash

關於Java集合類庫中的常用隊列

rac syn text 刪除 style asdasd 新元素 其他 arraylist Java中幾種常用的隊列 阻塞隊列與普通隊列的區別在於,當隊列是空的時,從隊列中獲取元素的操作將會被阻塞,或者當隊列是滿時,往隊列裏添加元素的操作會被阻塞。試圖從空的阻塞隊列中獲取元

java-常用數據庫的JDBCURL

ron log 矽谷 sqlserver com src 課件 ges cal 以mysql的jdbcUrl為例: 1、對於 Oracle 數據庫連接,采用如下形式: jdbc:oracle:thin:@localhost:1521:sid 2、對於 SQLSer

垃圾收集與常用的垃圾收集算法

嘗試 產生 統一 規則 存在 允許 成本 garbage 找到 前言:   首先思考垃圾收集(Garbage Collection,GC)需要完成的三件事情   1)哪些內存需要回收?   2)什麽時候回收?   3)如何回收?   再上一個博客中提到了Java內存運行時