淺談PHP源碼三十一:PHP內存池中的堆(heap)層基礎
【概述】
PHP的內存管理器是分層(hierarchical)的。這個管理器共有三層:存儲層(storage)、堆(heap)層和 emalloc/efree 層。在PHP源碼閱讀筆記三十:PHP內存池中的存儲層中介紹了存儲層,存儲層通過 malloc()、mmap() 等函數向系統真正的申請內存,并通過 free() 函數釋放所申請的內存。存儲層通常申請的內存塊都比較大,這里申請的內存大并不是指storage層結構所需要的內存大,只是堆層通過調用存儲層的分配方法時,其以段的格式申請的內存比較大,存儲層的作用是將內存分配的方式對堆層透明化。
在存儲層之上就是今天我們要了解的堆層。堆層一個調度層,它與上面的emalloc/efree層交互,將通過存儲層申請到的大塊內存,進行拆分,按需提供。在堆層中有其一套內存的調度策略,這個整個PHP內存分配管理的核心區域。
以下的所有分享都是基于ZEND_DEBUG未打開的情況。
首先看下堆層所涉及到的結構:
【結構】
/* mm block type */typedef struct _zend_mm_block_info {size_t _size;/* block的大小*/size_t _prev;/* 計算前一個塊有用到*/} zend_mm_block_info; typedef struct _zend_mm_block {zend_mm_block_info info;} zend_mm_block; typedef struct _zend_mm_small_free_block {/* 雙向鏈表 */zend_mm_block_info info;struct _zend_mm_free_block *prev_free_block;/* 前一個塊 */struct _zend_mm_free_block *next_free_block;/* 后一個塊 */} zend_mm_small_free_block;/* 小的空閑塊*/ typedef struct _zend_mm_free_block {/* 雙向鏈表 + 樹結構 */zend_mm_block_info info;struct _zend_mm_free_block *prev_free_block;/* 前一個塊 */struct _zend_mm_free_block *next_free_block;/* 后一個塊 */ struct _zend_mm_free_block **parent;/* 父結點 */struct _zend_mm_free_block *child[2];/* 兩個子結點*/} zend_mm_free_block; struct _zend_mm_heap {int use_zend_alloc;/* 是否使用zend內存管理器 */void *(*_malloc)(size_t);/* 內存分配函數*/void (*_free)(void*);/* 內存釋放函數*/void *(*_realloc)(void*, size_t);size_t free_bitmap;/* 小塊空閑內存標識 */size_t large_free_bitmap; /* 大塊空閑內存標識*/size_t block_size;/* 一次內存分配的段大小,即ZEND_MM_SEG_SIZE指定的大小,默認為ZEND_MM_SEG_SIZE (256 * 1024)*/size_t compact_size;/* 壓縮操作邊界值,為ZEND_MM_COMPACT指定大小,默認為 2 * 1024 * 1024*/zend_mm_segment *segments_list;/* 段指針列表 */zend_mm_storage *storage;/* 所調用的存儲層 */size_t real_size;/* 堆的真實大小 */size_t real_peak;/* 堆真實大小的峰值 */size_t limit;/* 堆的內存邊界 */size_t size;/* 堆大小 */size_t peak;/* 堆大小的峰值*/size_t reserve_size;/* 備用堆大小*/void *reserve;/* 備用堆 */int overflow;/* 內存溢出數*/int internal;#if ZEND_MM_CACHEunsigned int cached;/* 已緩存大小 */zend_mm_free_block *cache[ZEND_MM_NUM_BUCKETS];/* 緩存數組/#endifzend_mm_free_block *free_buckets[ZEND_MM_NUM_BUCKETS*2];/* 小塊空閑內存數組 */zend_mm_free_block *large_free_buckets[ZEND_MM_NUM_BUCKETS];/* 大塊空閑內存數組*/zend_mm_free_block *rest_buckets[2];/* 剩余內存數組 */ };
對于heap結構中的內存操作函數,如果use_zend_alloc為否,則使用malloc-type 內存分配,此時所有的操作就不經過堆層中的內存管理,直接采用malloc等函數。
compact_size的大小默認為 2 * 1024 * 1024(2M),如果有設置變量ZEND_MM_COMPACT則為此指定大小,如果內存的峰值超過這個值,則會調用storage的compact函數,只是這個函數現在的實現為空,可能在后續的版本中添加。
reserve_size為備用堆的大小,默認情況下為ZEND_MM_RESERVE_SIZE,其大小為(8*1024)
*reserve為備用堆,其大小為reserve_size,其用作內存溢出時報告錯誤用。
【關于USE_ZEND_ALLOC】
html' target='_blank'>環境變量 USE_ZEND_ALLOC 可用于允許在運行時選擇 malloc 或 emalloc 內存分配。使用 malloc-type 內存分配將允許外部調試器觀察內存使用情況,而 emalloc 分配將使用 Zend 內存管理器抽象,要求進行內部調試。
[zend_startup() - start_memory_manager() - alloc_globals_ctor()]
static void alloc_globals_ctor(zend_alloc_globals *alloc_globals TSRMLS_DC){char *tmp;alloc_globals- mm_heap = zend_mm_startup(); tmp = getenv( USE_ZEND_ALLOC if (tmp) {alloc_globals- mm_heap- use_zend_alloc = zend_atoi(tmp, 0);if (!alloc_globals- mm_heap- use_zend_alloc) {/* 如果不使用zend的內存管理器,同直接使用malloc函數*/alloc_globals- mm_heap- _malloc = malloc;alloc_globals- mm_heap- _free = free;alloc_globals- mm_heap- _realloc = realloc;}}
【初始化】
[zend_mm_startup()]
初始化storage層的分配方案,初始化段大小,壓縮邊界值,并調用zend_mm_startup_ex()初始化堆層。
[zend_mm_startup() - zend_mm_startup_ex()]
【內存對齊】
在PHP的內存分配中使用了內存對齊,內存對齊計算顯然有兩個目標:一是減少CPU的訪存次數;第二個就是還要保持存儲空間的效率足夠高。
# define ZEND_MM_ALIGNMENT 8 #define ZEND_MM_ALIGNMENT_MASK ~(ZEND_MM_ALIGNMENT-1) #define ZEND_MM_ALIGNED_SIZE(size)(((size) + ZEND_MM_ALIGNMENT - 1) ZEND_MM_ALIGNMENT_MASK) #define ZEND_MM_ALIGNED_HEADER_SIZEZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_block)) #define ZEND_MM_ALIGNED_FREE_HEADER_SIZEZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_small_free_block))
PHP在分配塊的內存中,用到內存對齊,如果所需要的內存的大小的低三位不為0(不能為8整除),則將低三位加上7,并~7進行與操作,即對于大小不是8的整數倍的內存大小補全到可以被8整除。
在win32機器上,一些宏對應的數值大小為:
ZEND_MM_MIN_SIZE=8
ZEND_MM_MAX_SMALL_SIZE=272
ZEND_MM_ALIGNED_HEADER_SIZE=8
ZEND_MM_ALIGNED_FREE_HEADER_SIZE=16
ZEND_MM_MIN_ALLOC_BLOCK_SIZE=8
ZEND_MM_ALIGNED_MIN_HEADER_SIZE=16
ZEND_MM_ALIGNED_SEGMENT_SIZE=8
如果要分配一個大小為9個字節的塊,則其實際分配的大小為ZEND_MM_ALIGNED_SIZE(9 + 8)=24
【塊的定位】
所分配的內存的右邊的兩位是用來標記內存的類型。
其大小的定義為#define ZEND_MM_TYPE_MASK ZEND_MM_LONG_CONST(0×3)
如下所示代碼為塊的定位
#define ZEND_MM_NEXT_BLOCK(b)ZEND_MM_BLOCK_AT(b, ZEND_MM_BLOCK_SIZE(b)) #define ZEND_MM_PREV_BLOCK(b)ZEND_MM_BLOCK_AT(b, -(int)((b)- info._prev ~ZEND_MM_TYPE_MASK)) #define ZEND_MM_BLOCK_AT(blk, offset)((zend_mm_block *) (((char *) (blk))+(offset))) #define ZEND_MM_BLOCK_SIZE(b)((b)- info._size ~ZEND_MM_TYPE_MASK)#define ZEND_MM_TYPE_MASKZEND_MM_LONG_CONST(0x3)
當前塊的下一個元素,即為當前塊的頭位置加上整個塊(去掉了類型的長度)的長度。
當前塊的上一個元素,即為當前塊的頭位置減去前一個塊(去掉了類型的長度)的長度。
關于前一個塊的長度,在塊的初始化時設置為當前塊的大小與塊類型的或操作的結果。
以上就是本文的全部內容,希望對大家的學習有所幫助,更多相關內容請關注PHP !
相關推薦:
淺談PHP源碼三十:PHP內存池中的存儲層
淺談PHP源碼二十九:關于接口的繼承
淺談PHP源碼二十八:關于類結構和繼承
以上就是淺談PHP源碼三十一:PHP內存池中的堆(heap)層基礎的詳細內容,PHP教程
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。
新聞熱點
疑難解答