1. 程式人生 > >PHP深入理解-記憶體管理

PHP深入理解-記憶體管理

記憶體管理的主要目的:提高記憶體利用率,減少記憶體碎片,提高記憶體分配的速度.

記憶體設計

最大的可分配單元是chunk,大小為2MB.每個chunk分配了512個page,page的大小為4KB.
zend的page_size與Linux一般的page_size大小相等.

zend內部使用了mmap分配記憶體,不足一記憶體頁的mmap強制以Linux的page_size進行對齊.
mmap可以指定這塊記憶體在不同程序之間的處理方式,是共享還是寫時拷貝.

相較於malloc分配方式,提供了檔案到記憶體對映的功能.malloc分配不是基於page_size對齊的.

#define ZEND_MM_CHUNK_SIZE (2 * 1024 * 1024)               /* 2 MB  */ 
#define ZEND_MM_PAGE_SIZE  (4 * 1024)                      /* 4 KB  */
#define ZEND_MM_PAGES      (ZEND_MM_CHUNK_SIZE / ZEND_MM_PAGE_SIZE)  /* 512 */

記憶體分配三種模式

  • small:小於等於3KB的記憶體
  • large:大於3KB小於等於(2MB減去4KB)記憶體
  • huge:大於2MB減去4KB記憶體

每個chunk中的4KB用於zend_mm_chunk結構體分配.

記憶體對齊

記憶體對其的意義:根據給定的記憶體地址快速定位在申請記憶體的位置.
實際上再申請的時候為了保證記憶體對齊,會多申請一塊記憶體,然後在這塊記憶體找到對齊的地址,除這個地址+申請的地址大小外的記憶體都將要被釋放.

zend提供了以下三種計算對齊的方式.

#define ZEND_MM_ALIGNED_OFFSET(size, alignment) \
	(((size_t)(size)) & ((alignment) - 1)) //得出基於對齊的起始地址的offset
#define ZEND_MM_ALIGNED_BASE(size, alignment) \
	(((size_t)(size)) & ~((alignment) - 1)) 
//得出對齊的起始地址 #define ZEND_MM_SIZE_TO_NUM(size, alignment) \ (((size_t)(size) + ((alignment) - 1)) / (alignment)) //根據size,alignment得出需分配的個數

記憶體資料結構

zend_mm_heap:
全域性變數alloc_globals.mm_heap指向zend_mm_heap資料結構

struct _zend_mm_heap {
#if ZEND_MM_CUSTOM
	int                use_custom_heap;
#endif
#if ZEND_MM_STORAGE
	zend_mm_storage   *storage;
#endif
#if ZEND_MM_STAT
	size_t             size;                    /* current memory usage */
	size_t             peak;                    /* peak memory usage */
#endif
	zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */
#if ZEND_MM_STAT || ZEND_MM_LIMIT
	size_t             real_size;               /* current size of allocated pages */
#endif
#if ZEND_MM_STAT
	size_t             real_peak;               /* peak size of allocated pages */
#endif
#if ZEND_MM_LIMIT
	size_t             limit;                   /* memory limit */
	int                overflow;                /* memory overflow flag */
#endif

	zend_mm_huge_list *huge_list;               /* list of huge allocated blocks */

	zend_mm_chunk     *main_chunk;
	zend_mm_chunk     *cached_chunks;			/* list of unused chunks */
	int                chunks_count;			/* number of alocated chunks */
	int                peak_chunks_count;		/* peak number of allocated chunks for current request */
	int                cached_chunks_count;		/* number of cached chunks */
	double             avg_chunks_count;		/* average number of chunks allocated per request */
#if ZEND_MM_CUSTOM
	union {
		struct {
			void      *(*_malloc)(size_t);
			void       (*_free)(void*);
			void      *(*_realloc)(void*, size_t);
		} std;
		struct {
			void      *(*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
			void       (*_free)(void*  ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
			void      *(*_realloc)(void*, size_t  ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
		} debug;
	} custom_heap;
#endif
};

chunk資料結構:

struct _zend_mm_chunk {
	zend_mm_heap      *heap;//AG()裡的mm_heap地址
	zend_mm_chunk     *next;//下一個trunk
	zend_mm_chunk     *prev;//之前的trunk
	int                free_pages;				/* number of free pages */
	int                free_tail;               /* number of free pages at the end of chunk 
    最後一塊連續可用的page*/
	int                num; //當前chunk的序號
	char               reserve[64 - (sizeof(void*) * 3 + sizeof(int) * 3)];
	zend_mm_heap       heap_slot;               /* 只用於mainchunk used only in main chunk */
	zend_mm_page_map   free_map;                /* 空閒頁的點陣圖512 bits or 64 bytes */
	zend_mm_page_info  map[ZEND_MM_PAGES];      /* 儲存每個頁的使用資訊,高兩位代表使用記憶體的型別,低十位區分是否連續的頁 2 KB = 512 * 4 */
};

記憶體分配釋放邏輯

如之前所述,記憶體分配分為三種不同的範圍.

huge

分配
1.申請size需要根據page_size進行對齊
2.對齊後的size再根據chunk_size大小進行對齊
3.將記憶體掛載到alloc_global.mm_heap->huge_list上

釋放: 從huge_list連結串列中刪除,呼叫munmap釋放.

large

large分配是page分配的整數倍.

1.遍歷雙向連結串列alloc_global.mm_heap->main_trunk
2.如果free_pages小於要申請的頁的個數回到1.
3.根據zend_mm_chunk->free_map查詢最優連續page(連續page個數最少,連續page編號最少).
4.如果查詢可分配的頁則返回對應的地址,並將map[page_num]標記為large記憶體
5.如果chunk都沒有可分配記憶體,就新申請一個chunk,在進行分配.

釋放:
將zend_mm_chunk->free_map[page_num],zend_mm_chunk->map[page_num]置為0.
然後修改free_pages.如果pages都釋放,那麼釋放chunk.

small分配

small型別共分為30種不同的大小.

mm_heap->free_slot陣列結構存著每個slot型別連結串列的首地址.

1.根據申請的記憶體查詢對應的規格表
2.根據規格表中的num,如果mm_heap->free_slot[num]為空則繼續下一步,如果不為空返回對應的地址,並從mm_heap->free_slot[num]指向連結串列的首地址刪除
3.申請的規格表中對應的頁數(bin_pages[bin_num])並更新mm_chunk->map[page_num]標識位為small記憶體.第一個頁需要設定map[page_num](位於map的24bit-16bit位段)設定free_slot個數.接下的連續頁的標誌位給予順序標誌(位於map的24bit-16bit位段).

釋放時:
直接插入mm_heap->free_slot當中.

GC機制

slot

依次遍歷mm_heap->free_slot.
根據slot的地址執行上述的查詢的巨集,找到對應的chunk地址以及當前slot所在的page_num,讀取map[page_num].
如果是slot連續申請的規格表中對應的頁數(bin_pages[bin_num]),那麼位於第一頁的map的24bit-16bit位段,會用於儲存這幾個連續頁的free_slot個數.其餘的頁的map的24bit-16bit位段用儲存連續頁的頁號.

如果map中的free_slot個數+1等於規格表中(bin_elements[num])存在的元素個數,那麼刪除heap->free_slot[num]連結串列中該slot.

chunk的遍歷每個page,如果是small記憶體的第一頁(map[page_num]&0x80000000),取24-16位判斷free_slot個數等於這個規格表規定的slot個數.釋放這幾個連續的頁.

chunk

迴圈遍歷chunk連結串列如果chunk->free_pages = 511那麼就釋放這個chunk.