C++性能優化(八)——內存分配機制

C++性能優化(八)——內存分配機制

1、操做系統內存佈局

一、32位系統經典內存佈局

Linux Kernel 2.6.7前版本採用的默認內存佈局形式以下:
C++性能優化(八)——內存分配機制
(1)32操做系統中,loader將可執行文件的各個段次依次載入到從0x80048000(128M)位置開始的空間中。應用程序可以訪問的最後地址是0xbfffffff(3G)的位置,3G以上的位置是給內核使用的,應用程序不能直接訪問。
(2)內存佈局從低地址到高地址依次爲:txet段、data段、bss段、heap、mmap映射區、stack棧區。
(3)heap和mmap是相對增加的,heap只有1G的虛擬地址空間可供使用。​
(4)stack空間不須要映射,用戶能夠直接訪問棧空間,所以是利用堆棧溢出進行的基礎。
起始1GB地址爲內核空間,隨後是向下增長的棧空間和由0x40000000向上增長的MMAP地址;堆空間從底部開始,去除ELF、數據段、代碼段、常量段後的地址,並向上增加。缺點是容易遭受溢出
,堆地址空間只有不到1GB。前端

二、32位系統默認內存佈局

Linux Kernel 2.6.7版本後32位操做系統的默認內存佈局方式以下:
C++性能優化(八)——內存分配機制
在經典內存佈局基礎上增長了Random offset隨機偏移,不容易遭受溢出***;堆地址向上增加,但MMAP向下增加,棧空間不是動態增加的,會受到限制;內存地址利用率較高。
棧自頂向下擴展,但棧有邊界,所以棧大小有限制(ulimit  -s查看)。堆自底向上擴展,mmap映射區自頂向下擴展,mmap和heap是相對擴展,直至消耗盡虛擬地址空間中的剩餘區域。算法

三、64位系統內存佈局

C++性能優化(八)——內存分配機制
64位操做系統的尋址空間比較大,沿用32位操做系統的經典內存佈局,增長隨機MMAP地址,防止溢出***。後端

2、操做系統內存管理機制

一、操做系統內存管理簡介

內存管理自底向上分爲三個層次:
(1)操做系統內核的內存管理。
(2)glibc層使用系統調用維護的內存管理算法。
(3)應用程序從glibc動態分配內存後,根據應用程序自己的程序特性進行優化,好比使用引用計數std::shared_ptr,內存池方式等等。
應用程序能夠直接使用系統調用從內核分配內存,根據程序特性本身維護內存,但會大大增長開發成本。數組

二、操做系統內存管理機制

Linux Kernel內存管理的基本思想是內存延遲分配,即只有在真正訪問一個地址的時候才創建地址的物理映射。Linux Kernel在用戶申請內存的時候,只分配一個虛擬地址,並無分配實際物理地址,只有當用戶使用內存時,Linux Kernel纔會分配具體的物理地址給用戶使用。
對於大內存,一般不一樣的內存分配方式都是直接MMAP;對於小數據,則經過向操做系統申請擴大堆頂,操做系統會把內存分頁映射到進程堆空間,再由malloc管理內存堆塊,減小系統調用;free內存時,不一樣內存分配方式有不一樣策略,不必定會將內存還給操做系統,所以若是訪問釋放的內存並不會當即Run Time Error,只有訪問的地址沒有對應的內存分頁纔會。
對於heap操做,操做系統提供brk系統調用,c運行庫提供sbrk庫函數;對於mmap映射區操做,操做系統提供了mmap和munmap系統調用。安全

三、heap操做系統調用接口

#include <unistd.h>
int brk(void *addr);
void *sbrk(intptr_t increment);

當參數increment爲0時,sbrk返回進程當前的brk值,increment爲正數時擴展brk值,increment爲負數時收縮brk值。
brk是系統調用,sbrk是庫函數。C語言的動態內存分配基本函數是malloc,在glibc中malloc函數調用庫函數sbrk,sbrk調用brk函數。brk系統調用只是簡單的改變mm_struct結構體的成員變量brkd的值。
C++性能優化(八)——內存分配機制
start_code和end_code是進程代碼段的起始和結束地址、
start_data和end_data是進程數據段的起始和終止地址。
start_stack是進程堆棧段的起始地址。
start_brk是進程動態內存分配的起始地址(堆的起始地址)。
brk是進程動態內存分配當前的終止地址(堆的當前最後地址)。性能優化

四、mmap操做系統調用接口

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
int munmap(void *addr, size_t length);

addr:映射區的開始地址。
length:映射區的長度。
prot:指望的內存保護標誌。
flags:指定映射對象的類型,映射選項和映射頁是否能夠共享。
fd:有效的文件描述符。
offset:被映射對象內容的起點。​
mmap函數將一個文件或者其它對象映射進內存,文件被映射到多個頁上,若是文件大小不是全部頁大小之和,最後一個頁不被使用的空間將會清零;munmap函數刪除特定地址區域的對象映射。多線程

3、ptmalloc2簡介

一、ptmalloc2簡介

ptmalloc/ptmalloc2/ptmalloc3版本基於dlmalloc進行開發,
Wolfram Gloger在Doug Lea的基礎上對dlmalloc進行了改進,使得ptmalloc能夠支持多線程。Linux操做系統默認使用glibc malloc版本,即ptmalloc2,glibc-2.3.x中已經集成了ptmalloc2,目前ptmalloc最新版本爲ptmalloc3。
ptmalloc內存分配器處於內核和用戶程序之間,用於響應用戶的內存分配請求,向操做系統申請內存,而後將內存返回給用戶程序。爲了保證高效,內存分配器通常都會預先分配一塊大於用戶請求的內並進行管理;用戶釋放掉的內存不會當即返回給操做系統,內存分配器會管理被釋放掉的空閒空間以應對用戶之後的內存分配請求。內存分配器不只須要管理分配的內存塊,還須要管理空間的內存塊。當響應用戶的請求時,內存分配器會首先在空閒空間中尋找一塊合適的內存返回給用戶,在空閒空間中找不到時纔會從新分配一塊新內存。架構

二、ptmalloc2多線程支持

dlmalloc內存分配器中只有一個主分配區,每次分配內存都必須對主分配區加鎖,分配完成後再釋放鎖。在多線程的環境下,對主分配區的鎖的競爭很激烈,嚴重影響內存分配效率。
ptmalloc內存分配器能夠支持多線程,增長了非主分配區支持,主分配區和非主分配區造成一個環形鏈表進行管理,每個分配區使用互斥鎖保證多線程訪問安全。每一個進程只有一個主分配區,能夠有多個非主分配區,ptmalloc根據系統對分配區的爭用狀況動態的增長非主分配區的個數,分配區的數量一旦增長就不會再減小。
主分配區能夠訪問進程的heap和mmap映射區域,即主分配區可使用brk、sbrk、mmap向操做系統申請虛擬內存;非主分配區只能訪問進程的mmap映射區域,非主分配區每次使用mmap系統調用向操做系統申請HEAP_MAX_SIZE(32位系統1M,64位系統64M)大小的虛擬內存,當用戶向非主分配區請求分配內存時再分割成小塊內存進行分配。
因爲系統調用的效率比較低,直接從用戶空間分配內存效率會比較高,所以ptmalloc只在必要狀況下才會調用mmap系統調用向操做系統申請內存。
主分配區能夠訪問heap區域,若是用戶不調用sbrk或者brk函數,分配程序就能夠保證分配到連續的虛擬內存,由於一個進程只有一個主分配區使用sbrk分配heap區域的虛擬內存。若是主分配區的內存是經過mmap向系統申請的,當free內存時,主分配區會直接調用munmap將內存歸還給操做系統。
當線程須要使用malloc函數分配內存空間時,線程會先查看線程的私有變量中是否已經存在有一個分配區。若是存在,嘗試對分配區加鎖,若是加鎖成功就使用相應分配區分配內存;若是失敗,線程就搜索環形鏈表試圖得到一個沒有加鎖的分配區來分配內存;若是全部分配區都加鎖,那麼malloc會開闢一個新分配區,把新分配區添加到循環鏈表中並加鎖,使用新分配區進行分配內存操做。
在釋放操做中,線程試圖得到待釋放內存塊所在的分配區的鎖,若是分配區正在被其它線程使用,則須要等待其它線程釋放分配區的互斥鎖後才能夠進行釋放操做。​
爲了支持多線程,多個線程能夠從同一個分配區中分配內存,ptmalloc假設線程A釋放掉一塊內存後,線程B會申請相似大小的內存,但A釋放的內存跟B須要的內存不必定徹底相等,可能有一個小的偏差,就須要不停地對內存塊做切割和合並,所以分配過程當中可能產生內存碎片。dom

三、ptmalloc2缺點

(1)ptmalloc收縮內存從top chunk開始,若是與top chunk相鄰的chunk不能釋放,top chunk如下都不能釋放。
(2)多線程鎖開銷大,須要避免多線程頻繁分配釋放。
(3)多線程使用內存不均衡時,容易致使內存浪費。
(4)每一個chunk至少8字節的開銷很大。
(5)不按期分配長生命週期的內存容易形成內存碎片,不利於回收。ide

4、ptmalloc2 BINS架構

一、chunk簡介

若每次申請內存都調用sbrk、brk、mmap,那麼每次都會產生系統調用,影響性能,而且容易產生內存碎片,由於堆是從低地址到高地址,若是高地址的內存沒有被釋放,低地址的內存就不能被回收。
ptmalloc使用內存池管理方式,採用邊界標記法將內存劃分紅不少塊,從而對內存的分配與回收進行管理。爲了高效分配內存,ptmalloc會預先向操做系統申請一塊內存供用戶使用,當申請和釋放內存時,ptmalloc會對相應內存塊進行管理,並經過內存分配策略判斷是否將其回收給操做系統,使用戶申請和釋放內存更加高效,避免產生過多的內存碎片。
用戶請求分配的內存在ptmalloc中使用chunk表示, 每一個chunk至少須要8個字節額外的開銷。 用戶釋放掉的內存不會立刻歸還操做系統,ptmalloc會統一管理heap和mmap區域的空閒chunk,避免了頻繁的系統調用。

struct malloc_chunk {
  INTERNAL_SIZE_T      prev_size;    /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;         /* Size in bytes, including overhead. */
  struct malloc_chunk* fd;           /* double links -- used only if free. */
  struct malloc_chunk* bk;
  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize;  /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

prev_size: 若是前一個chunk空閒,表示前一個chunk的大小;若是前一個chunk不空閒,字段無心義。 
size:當前chunk的大小,而且記錄了當前chunk和前一個chunk的一些屬性,包括前一個chunk是否在使用中,當前chunk是不是經過mmap得到的內存,當前chunk是否屬於非主分配區。 
fd和bk:指針fd和bk只有當chunk空閒時才存在,用於將對應的空閒chunk塊加入到空閒chunk塊鏈表中統一管理,若是chunk 塊被分配給應用程序使用,chunk塊被從空閒chunk鏈表中拆出,那麼這兩個指針也就沒有用了,因此也看成應用程序的使用空間,而不至於浪費。 
fd_nextsize和bk_nextsize:當前chunk存在於large bins 中時, large bins中空閒chunk按照大小排序,但同一個大小的 chunk可能有多個,增長fd_nextsize和bk_nextsize字段能夠加快遍歷空閒chunk ,並查找知足須要的空閒chunk, fd_nextsize 指向下一個比當前chunk大小大的第一個空閒chunk, bk_nextsize指向前一個比當前chunk大小小的第一個空閒 chunk 。
使用中的chunk結構以下:
C++性能優化(八)——內存分配機制
chunk指針指向chunk開始的地址;
mem指針指向用戶內存塊開始的地址。
P=0時,表示前一個chunk爲空閒,prev_size纔有效;P=1時,表示前一個chunk正在使用,prev_size無效;P主要用於內存塊的合併操做。ptmalloc分配的第一個塊老是將P設爲1,以防止程序引用到不存在的區域。​
M=1爲mmap映射區域分配;M=0爲heap區域分配。
A=1爲非主分區分配;A=0爲主分區分配。
空閒chunk結構以下:
C++性能優化(八)——內存分配機制
空閒的chunk會被放置到空閒的鏈表bins上。當用戶申請分配內存時,會先去查找空閒鏈表bins上是否有合適的內存。
fp和bp分別指向前一個和後一個空閒的chunk。
fp_nextsize和bp_nextsize分別指向前一個空閒chunk和後一個空閒chunk的大小,用於在空閒鏈表上快速查找合適大小的chunk。

二、BINS架構簡介

ptmalloc將相同大小的chunk用雙向鏈表連接起來,稱爲bin。ptmalloc共維護128個bin,並使用一個數組來存儲bin。
C++性能優化(八)——內存分配機制
數組中第1個bin爲unsorted bin,若是被用戶釋放的chunk大於max_fast或者fast bins中的空閒chunk合併後,chunk首先會被放到unsorted bin隊列中。若是unsorted bin不能知足分配要求,ptmalloc會將unsorted bin中的chunk加入bins中,而後再從bins中繼續進行查找和分配過程。unsorted bin是bins的一個緩衝區,能夠加快內存分配速度。
數組中第2個至第64個的bin稱爲small bin,同一個small bin 中的chunk具備相同的大小,相鄰的small bin中的chunk大小相差爲8字節。
數組中第65個開始bin稱爲large bin。large bins中的每個 bin分別包含了一個給定範圍內的chunk,其中chunk按大小序排列,相同大小的chunk按照最近使用順序排列。

三、Fast Bins

程序在運行時會常常須要申請和釋放小塊內存空間。當內存分配器合併相鄰的若干chunk後,可能當即會有另外一個小塊內存的請求,內存分配器須要從大塊空閒內存中分割出一塊內存,效率低下,所以ptmalloc在分配過程當中引入了fast bins。
fast bins是bins的高速緩衝區,每一個fast bin是空閒chunk的單鏈表,採用單鏈表是因爲fast bin中增刪chunk都發生在鏈表的前端。fast bins中包含7個fast bin,fast bin中的chunk大小以8字節遞增,分別爲1六、2四、3二、40、4八、5六、64。 
當用戶申請分配不大於max_fast(默認值64字節)內存塊時,ptmalloc首先會先到fast bins中相應chunk尺寸的fast bin尋找是否有合適chunk,在fast bin中被檢索出的第一個chunk將被摘除並返回給用戶;當用戶釋放一塊不大於max_fast(默認值64字節)的chunk時,默認會被放到fast bins中chunk尺寸大小的fast bin的前端,除非特定狀況,兩個相鄰空閒chunk並不會被合併成一個空閒chunk,不合並可能會致使碎片化問題,但能夠大大加速釋放過程。

四、Unsorted Bin

Unsorted Bin存儲在bins數組的第1個,是bins的緩衝區,用於加快內存分配速度。當用戶釋放的chunk大於max_fast或者fast bins中合併後的chunk都會首先進入unsorted bin上。Unsorted Bin用雙向鏈表管理chunk,chunk無大小限制,任何大小chunk均可以添加進Unsorted Bin。Unsorted Bin提供了ptmalloc重複使用最近釋放的chunk,所以內存的分配和釋放會更快。 
用戶申請分配內存時,若是在fast bins中沒有找到合適的chunk,則ptmalloc會先在Unsorted Bin中查找合適的空閒chunk;若是沒有合適的chunk,ptmalloc會將unsorted bin上的chunk放入bins上,而後繼續到bins上查找合適的空閒chunk。 

五、Small Bins

小於512字節的chunk即small chunk,保存small chunk的bin即small bin。bins數組從第2至第64的63個bin爲Small Bin,Small Bins中相鄰bin之間相差8個字節,同一個Small Bin中的chunk具備相同大小,第0個Small Bin中的chunk大小爲16字節,第62個Small Bin中的chunk大小爲512字節。
每一個Small Bin是一個空閒chunk的雙向循環鏈表,釋放掉的chunk添加在鏈表的前端,而分配出的chunk則從鏈表後端摘除並返回給用戶。 
分配chunk時,從相應chunk大小的small bin中摘除最後一個chunk並返回給用戶;釋放chunk時,ptmalloc會檢查相鄰chunk是否空閒,如果則將相鄰chunk從所屬的small bin中摘除併合併成一個新chunk,新chunk會添加在unsorted bin的前端;合併chunk消除了碎片化的影響但減慢了釋放速度。 

六、Large Bins

大小大於等於512字節的chunk即Large Chunk,保存Large Chunk的bin即Large Bin,位於bins數組中small bins後。Large Bins中的每個bin分別包含了一個給定範圍內的chunk,其中的chunk按大小遞減排序,大小相同則按照最近使用時間排列。 
Large Bins包含63個bin,每一個bin中的chunk大小不是一個固定的等差數列,而是分紅6組;每組bin中chunk數量是一個固定的等差數列,依次爲3二、1六、八、四、二、1,chunk大小公差依次爲64B、512B、4096B、32768B、262144B等。

七、Top Chunk 

top chunk是分配區的頂部空閒內存,當bins上都不能知足內存分配要求時,就會在top chunk上進行分配。 
當top chunk大小比用戶所請求大小還大的時候,top chunk會分割爲兩個部分:User chunk(用戶請求大小)和Remainder chunk(剩餘大小),Remainder chunk會成爲新的top chunk;當top chunk大小小於用戶所請求chunk大小時,top chunk會經過sbrk(主分配區)或mmap(非主分配區)系統調用來擴容。 ​
主分配區是惟一可以映射進程heap區域的分配區,能夠經過sbrk來增大和收縮進程heap的大小。ptmalloc在開始的時候會預先分配一塊較大的空閒內存。主分配區的top chunk在第一次調用malloc時會分配一塊空間做爲初始化的heap。
非主分配區會預先從mmap區域分配一塊較大的空閒內存模擬 sub-heap,經過管理sub-heap來響應用戶的需求,由於內存是按地址從低向高進行分配的,在空閒內存的最高處存在着一塊空閒 chunk,即top chunk。當fast bin和bins都知足不了用戶的內存分配需求時,ptmalloc會從top chunk分出一塊內存給用戶,若是top chunk空間不足,會從新分配一個sub-heap,將top chunk遷移到新的sub-heap上。在分配過程當中,top chunk的大小隨着切割動態變化。
主分配區是惟一可以映射進程heap區域的分配區,能夠經過sbrk來增大或收縮進程heap大小。top chunk在heap的最上面,若是申請內存時,top chunk空間不足,ptmalloc會調用sbrk將進程heap的邊界brk上移,而後修改top chunk的大小。

八、mmaped chunk 

當須要分配的chunk足夠大,fast bins和bins都不能知足要求,甚至top chunk都不能知足分配需求時,ptmalloc會使用mmap來直接使用頁映射機制來將頁映射到進程空間,放到mmaped chunk上,當釋放mmaped chunk上內存的時候會直接交還給操做系統。
mmap分配閾值默認爲128KB,分配閾值能夠動態調整。若是開啓mmap分配閾值的動態調整機制,而且當前回收的chunk大小大於mmap分配閾值,將mmap分配閾值設置爲chunk的大小,將mmap收縮閾值設定爲mmap分配閾值的2倍。

九、Last remainder chunk 

Last remainder chunk是一種特殊chunk,不會在任何bins中找到。當須要分配一個small chunk,但在small bins中找不到合適的chunk,若是last remainder chunk的大小大於所須要的small chunk大小,last remainder chunk被分割成兩個chunk,其中一個chunk返回給用戶,另外一個chunk變成新的last remainder chunk。

相關文章
相關標籤/搜索