這篇文章主要介紹了關於淺談PHP源碼三十一:PHP內存池中的堆(heap)層基礎,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下
淺談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_CACHE unsigned int cached;/* 已缓存大小 */ zend_mm_free_block *cache[ZEND_MM_NUM_BUCKETS];/* 缓存数组/ #endif zend_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】
環境變數 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_IGNED_FREE_HEADEREDED_FREE_6L_JJR_J3_7_IGNED_FREE_HEA _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記憶體池中的儲存層#1原始碼二十九:關於介面的繼承#淺聊PHP原始碼二十八:關於類別結構與繼承以上是淺談PHP原始碼三十一:PHP記憶體池中的堆(heap)層基礎的詳細內容。更多資訊請關注PHP中文網其他相關文章!