一、為什麼需要記憶體池
記憶體是非常寶貴的資源,需要最優訪問;
作業系統適合管理大塊記憶體,如一頁(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、記憶體分配時先從作業系統分配較大塊記憶體,分配完後放入上述相應的陣列中,方便下次使用。
php 增加陣列下標 PHP獲取陣列下標的問題
php 獲取陣列指定值的位置或下標 如下 定義乙個陣列 array array 0 a 1 b 2 c 3 d 使用 array search 要搜尋的值 陣列 key array search b array key 1 key array search a array key 0 擴充套件資料 ...
for迴圈 php 增加陣列維數 PHP 多維陣列
php 兩維陣列 兩維陣列是陣列的陣列 三維陣列是陣列的陣列的陣列 首先,讓我們看看下面的 品牌庫存 銷量volvo bmwsaab land rover 我們能夠在兩維陣列中儲存上表中的資料,就像這樣 cars array array volvo 22,18 array bmw 15,13 arr...
增加陣列下標 陣列操作之增刪改查
js中有6種資料型別 5種基本資料型別 number,string,boolean,undefined,null 1種複雜資料型別 object 陣列 array 這一複雜資料型別在前端開發過程中是比較常見的一種,因此陣列有很多的操作方法。在程式語言的學習中,除了基礎必會的hello world入門...