一、為什麼需要記憶體池
記憶體是非常寶貴的資源,需要最優訪問;
作業系統適合管理大塊記憶體,如一頁(4096位元組),不適合小塊記憶體分配;不做記憶體池管理,容易產生記憶體碎片,會出現剩餘記憶體夠,但沒有一塊連續記憶體來分配,會引起作業系統把程式hold住來整理碎片的情況;
另外直接呼叫作業系統分配記憶體會導致從使用者態切換到核心態,開銷比較大;
二、記憶體池設計目標:
1、化零為整,減少系統呼叫;
2、不出現記憶體洩露;
3、高效,盡量無鎖設計;
三、php記憶體池實現
這是官方的示意圖,其中free_buckets代表小塊記憶體列表,large_free_buckets代表大塊記憶體列表,還有乙個rest_buckets, 鳥哥的解釋是:"這個結構是個雙向列表, 用來儲存一些php分配後剩下的記憶體, 避免無意義的把剩餘記憶體插入free_buckets帶來的效能問題"。
對於小塊記憶體, php還引入了cache機制:
引入cache機制希望做到,一次定位就能查詢分配。
其中free_bitmap和和large_free_bitmap為位圖,指示對應位相應的記憶體索引是否有空閒記憶體。
下面會具體說明php是如何管理記憶體,在說明之前先說明下環境,筆記實驗的機器是64位的,php版本為5.6.2,下面的資料都是基於這個前提。
php記憶體管理主要是圍繞free_buckets和large_free_buckets這二個陣列來 展開的,這二個陣列都是乙個長度為64的陣列。
1、小塊記憶體管理
free_buckets管理長度小於等於528位元組的記憶體,free_buckets[0]管理長度為長度16-23位元組的記憶體,free_buckets[1]管理長度為24-31位元組的記憶體,依此類推……
其中每一項又是乙個雙向鍊錶,講起來比較抽象,我們來畫圖描述下分配和釋放記憶體後記憶體的布局吧。
剛開始free_buckets陣列每項都為null:
歸還32位元組記憶體後
歸還36位元組記憶體後
下次假設要分配長度32-39位元組之間的記憶體如35,直接從下標2中遍歷元素,只要哪個元素的長度大於等於要分配的長度,即將長度為36的記憶體歸還。
接下來我們看下小塊記憶體的分配是怎麼處理的,為了保證記憶體分配的高效,php每次會從作業系統分配大塊記憶體,預設是256kb,可以通過環境變數zend_mm_seg_size來設定。
從作業系統分配記憶體後,php會根據前面的換算關係,將記憶體塊放到相應的記憶體塊中,便於後續快速分配。
2、大塊記憶體管理
小塊記憶體管理長度小於等於528(參考巨集zend_mm_max_small_size的定義)位元組的記憶體,大於528的都由large_free_buckets來管理,large_free_buckets也是長度為64的陣列,每個下標管理的記憶體範圍是前開閉區間,設下標為i,則管理記憶體長度為[2^i, 2^(i+1))。
舉幾個例子,large_free_buckets[9]的下標為9,2的9次方是512, 所以其管理長度為512-1023之間的記憶體;
large_free_buckets[10]管理長度為1024-2047之間的記憶體;large_free_buckets[11]管理長度為2048-4095之間的記憶體……
這樣一共可以管理最大2^64的記憶體,當然實際不會用這麼多,因為php有記憶體限制相關引數。
可以看到,在大塊記憶體的設計時,並沒有和小塊記憶體一樣每個下標管理的記憶體長度差為8,而是下乙個下標管理的長度為上乙個下標管理的長度的2倍;之所以這樣設計,因為大塊記憶體比較大,不用太細的管理,另外就是要盡量節省記憶體,如果相鄰下標管理記憶體長度差為8位元組,則需要很大的陣列來管理這些記憶體。
這樣設計還會有個問題,可能會造成巨大的記憶體浪費,如下標10管理1024-2047之間的記憶體,如果釋放一塊長度為2046的記憶體,但申請的時候只要1030位元組,則多餘的1016位元組就白白浪費了,對於這個問題,php通過樹和雙向列表來管理:
什麼意思呢,結合釋放記憶體的過程說明下:
1)、釋放2048位元組記憶體
結合前面講的,落在下標的11元素上,2048的二進位制是100000000000,其中第1個1表示落在哪個下標中,這裡從右到左數排第12位,從0開始計算就是第11位;
從左到右數第二位是0,所以放到下標1這個元素的左子樹上。
2、釋放3100位元組記憶體
3100的二進位制是110000011100,從左到右數第二位是1,所以放在右子樹上。
3、釋放4093位元組記憶體
4093的二進位制是111111111101,從左到右第二位是1,放右子樹上,發現右子樹已經有了3100,再往右數,第三位還是1,所以放到3100的右子樹上
申請的時候就是掃這個順序掃瞄的,如果對應二進位制位為0,則掃瞄左子樹,如果為1則掃瞄右子樹。
舉個例子,這時如果要申請2900位元組記憶體,轉成二進位制為101101010100,從左往右第2位為1,所以走到3100這裡就返回了,而不會分配到4093位元組的記憶體。
四、總結
1、php的記憶體分配主要是圍繞兩個陣列來展開:free_buckets和large_free_buckets,其中前者用來管理小塊記憶體,後者用來管理大塊記憶體。
2、對於小塊記憶體,做到盡量可以再次使用,分成64個區段,每段管理的記憶體位元組間隔為8,即下標為0管理16-23,下標1管理24-31,依此類推……
3、對於大塊記憶體,陣列不宜過大,所以陣列的長度也是64,但是為了不浪費記憶體,採用樹+雙向列表來管理,方便快速查詢,也不會浪費太多記憶體。
4、記憶體分配時先從作業系統分配較大塊記憶體,分配完後放入上述相應的陣列中,方便下次使用。
記憶體池 C 記憶體池
c c 下記憶體管理是讓幾乎每乙個程式設計師頭疼的問題,分配足夠的記憶體 追蹤記憶體的分配 在不需要的時候釋放記憶體 這個任務相當複雜。1.呼叫malloc new,系統需要根據 最先匹配 最優匹配 或其他演算法在記憶體空閒塊表中查詢一塊空閒記憶體,呼叫free delete,系統可能需要合併空閒記...
C 記憶體管理 記憶體池
很多內容來自於網際網路,如有侵權,請告知。另外,從 收穫很多,在此表示感謝。我們寫程式經常需要 malloc 和 new 一塊記憶體出來,這些記憶體是在堆上進行分配的,在堆上分配的記憶體和在棧上分配的記憶體不同,可以長久的儲存。堆是什麼 可以把你的程序空間 想象成 4g 大小的記憶體 32 為機子上...
C 記憶體管理 記憶體池
引子 一 單獨類記憶體池 classa intget void operator new size t void operator delete void size t private a next static a freestore 指向可用首位址 static const int achunk...