在C中直接使用malloc、free進行記憶體的分配和釋放,但頻繁的分配和釋放記憶體會產生記憶體片段、降低系統效能,php的變數的分配和釋放會非常的頻繁、若直接通過malloc的方式愛進行分配、會造成嚴重的效能問題,作為語言級的應用、這種損耗不太能接受、所以php實現了自己的記憶體池ZendMM,來替代glibc的malloc和free、以解決記憶體頻繁分配、釋放的問題。
它定義了chunk、page、slot三種粒度的記憶體操作,每個chunk的大小為2MB、每個page 4kB,一個chunk切割成512個page、每個page切割成若干個slot,申請記憶體時、按照申請的大小、執行不同的分配策略
huge:大於2MB,直接調用系統分配,分配若干個chunk
large:申請記憶體大於3092B(3/4page)、小於2044kb(511page)分配若干個page
small:申請記憶體小於3092B,記憶體池提前定義好了30種不同大小的記憶體(8、16、32...、3072),它們分布在不同的page上、申請記憶體時直接在相應的記憶體上尋找相應的slot,記憶體池通過zend_mm_heap結構儲存記憶體池的主要資訊,比如大記憶體鏈表、chunk鏈表、slot各大小記憶體鏈表等
大記憶體配置的實際上是若干個chunk,然後通過一個zend_mm_huge_list結構進行管理,大記憶體之間構成單向鏈表
chunk是記憶體池向系統申請、釋放記憶體的最小粒度,chunk之間構成雙向鏈表,第一個chunk的地址儲存於zend_mm_heap->main_chunk,第一個page的記憶體用於儲存chunk自己的結構體成員,比如前後chunk的指標、當前chunk各page的使用方式等
相同大小的slot之間構成單鏈表
記憶體池的初始化在php_module_startup階段完成、初始化過程主要是分配heap結構,這個過程在start_memory_manager過程完成,如果是多線程環境,則會為每一個線程分配一個記憶體池,線程之間互不影響,初始化時會根據環境變數use_zend_alloc_huge_pages來設定是否開啟記憶體大頁,非安全執行緒環境下、分配的heap會儲存到alloc_globals中也就是AG宏,需要注意的是,zend_mm_heap這個結構不是單獨分配的,嵌入在chunk結構體中,,因為chunk結構體佔用了一個page、但實際上它用不了那麼大的記憶體,為了儘可能利用空間、就把它放在了這裡
記憶體配置: 1. huge超過2MB的記憶體配置,分配時,會將它對齊到n個chunk,還會分配一個zned_mm_huge_list結構,管理所有的huge記憶體,記憶體對齊的過程是記憶體池在申請後自己調整的,而不是簡單的由作業系統來完成,會先按照實際大小申請一次,如果剛好可以達到記憶體對齊、無需調整、直接返回使用,如果不是ZEND_MM_CHUNK_SIZE(2MB)的整數倍,則zendMM會釋放掉這塊兒記憶體,然後按照實際記憶體大小+ZEND_MM_CHUNK_SIZE再申請一次,多申請的那塊是用來調整的 2. large分配:申請的記憶體大小在3072B(3/4page)和2044k(511個page)之間時,記憶體池會在chunk上尋找對應數量的page返回,large的申請粒度是page,chunk上有free_map和map兩個成員用於記錄page的分配資訊
free_map就是512bit,用來記錄該chunk上page的分配情況,已使用則置位1
map用來記錄page的配置類型及分配的page頁數,每個page對應一個數群組成員,最高2位記錄page的配置類型、01是large、10是small,分配時從第一個chunk開始遍曆、依次尋找chunk是否有滿足要求的page,若當前chunk沒有合適的、則尋找下一個chunk,若直到最後都沒有合適的、則重新分配一個chunk,申請時,不知誰尋找到足夠頁數的page、而是儘可能的填滿chunk的空隙,儘可能的與已指派的page串連在一起,避免中間出現page空隙(以減少後續分配時的尋找次數) 1)從第一個chunk分組(0~63)開始檢查,當前分組有可用page、先檢測當前page的bit位、找到第一個和最後一個空閑page的位置,如果不夠則把這些page都標記為1(已指派)、進行尋找其它分組,如果page恰好、則直接使用、中斷檢索,如果page比需要的大、表示可用,但不是最優的,會接著尋找其它chunk直到最後比較出來一個最優的(可最大程度利用page的) 2)找到合適的page後、就設定相應的page資訊、即free_map和map資訊然後返回page地址 3. small分配:
會先檢查相應規格的記憶體是否已指派、若未分配或者分配已使用完、則申請相應頁數的page、page的分配過程與large分配一致,申請到page之後,按照固定大小切割成slot、slot之間用單鏈表串連,鏈表頭部儲存至AG(mm_heap)->free_slot
記憶體池釋放的粒度是chunk,通過efree來完成1. huge記憶體的釋放、large、smal類型的因為chunk的第一個page被佔用、所以不可能是相對chunk的位移量為0,由此區分chunk類型和large、small類型,釋放時,之間將佔用的chunk釋放、同時從AG鏈表刪除2. large記憶體的釋放:若計算得到offset不為0,表示該地址是large或者small記憶體、然後根據offset算出是第幾個page、得到page之後可以從chunk->map中得到page的配置類型、就可以釋放指定類型的記憶體了,large並不會直接釋放、而是將page的分配資訊置為未分配、若釋放後、發現該chunk下都是未分配的,則釋放chunk、釋放時優選選擇把chunk移到AG,緩衝數達到一定值後就不再繼續緩衝新加入的chunk、將記憶體歸還系統、避免佔用過多的記憶體,在分配chunk時若發現chached_chunks中有緩衝的chunk直接取出使用、不向系統發出申請3. small類型的釋放、直接將釋放的slot插回該規則slot的可用鏈表的頭部即可、比較簡單
在C中直接使用malloc、free進行記憶體的分配和釋放,但頻繁的分配和釋放記憶體會產生記憶體片段、降低系統效能,php的變數的分配和釋放會非常的頻繁、若直接通過malloc的方式愛進行分配、會造成嚴重的效能問題,作為語言級的應用、這種損耗不太能接受、所以php實現了自己的記憶體池ZendMM,來替代glibc的malloc和free、以解決記憶體頻繁分配、釋放的問題
它定義了chunk、page、slot三種粒度的記憶體操作,每個chunk的大小為2MB、每個page 4kB,一個chunk切割成512個page、每個page切割成若干個slot,申請記憶體時、按照申請的大小、執行不同的分配策略
huge:大於2MB,直接調用系統分配,分配若干個chunk
large:申請記憶體大於3092B(3/4page)、小於2044kb(511page)分配若干個page
small:申請記憶體小於3092B,記憶體池提前定義好了30種不同大小的記憶體(8、16、32...、3072),它們分布在不同的page上、申請記憶體時直接在相應的記憶體上尋找相應的slot,記憶體池通過zend_mm_heap結構儲存記憶體池的主要資訊,比如大記憶體鏈表、chunk鏈表、slot各大小記憶體鏈表等
大記憶體配置的實際上是若干個chunk,然後通過一個zend_mm_huge_list結構進行管理,大記憶體之間構成單向鏈表
chunk是記憶體池向系統申請、釋放記憶體的最小粒度,chunk之間構成雙向鏈表,第一個chunk的地址儲存於zend_mm_heap->main_chunk,第一個page的記憶體用於儲存chunk自己的結構體成員,比如前後chunk的指標、當前chunk各page的使用方式等
相同大小的slot之間構成單鏈表
記憶體池的初始化在php_module_startup階段完成、初始化過程主要是分配heap結構,這個過程在start_memory_manager過程完成,如果是多線程環境,則會為每一個線程分配一個記憶體池,線程之間互不影響,初始化時會根據環境變數use_zend_alloc_huge_pages來設定是否開啟記憶體大頁,非安全執行緒環境下、分配的heap會儲存到alloc_globals中也就是AG宏,需要注意的是,zend_mm_heap這個結構不是單獨分配的,嵌入在chunk結構體中,,因為chunk結構體佔用了一個page、但實際上它用不了那麼大的記憶體,為了儘可能利用空間、就把它放在了這裡
記憶體配置: 1. huge超過2MB的記憶體配置,分配時,會將它對齊到n個chunk,還會分配一個zned_mm_huge_list結構,管理所有的huge記憶體,記憶體對齊的過程是記憶體池在申請後自己調整的,而不是簡單的由作業系統來完成,會先按照實際大小申請一次,如果剛好可以達到記憶體對齊、無需調整、直接返回使用,如果不是ZEND_MM_CHUNK_SIZE(2MB)的整數倍,則zendMM會釋放掉這塊兒記憶體,然後按照實際記憶體大小+ZEND_MM_CHUNK_SIZE再申請一次,多申請的那塊是用來調整的 2. large分配:申請的記憶體大小在3072B(3/4page)和2044k(511個page)之間時,記憶體池會在chunk上尋找對應數量的page返回,large的申請粒度是page,chunk上有free_map和map兩個成員用於記錄page的分配資訊
free_map就是512bit,用來記錄該chunk上page的分配情況,已使用則置位1
map用來記錄page的配置類型及分配的page頁數,每個page對應一個數群組成員,最高2位記錄page的配置類型、01是large、10是small,分配時從第一個chunk開始遍曆、依次尋找chunk是否有滿足要求的page,若當前chunk沒有合適的、則尋找下一個chunk,若直到最後都沒有合適的、則重新分配一個chunk,申請時,不知誰尋找到足夠頁數的page、而是儘可能的填滿chunk的空隙,儘可能的與已指派的page串連在一起,避免中間出現page空隙(以減少後續分配時的尋找次數) 1)從第一個chunk分組(0~63)開始檢查,當前分組有可用page、先檢測當前page的bit位、找到第一個和最後一個空閑page的位置,如果不夠則把這些page都標記為1(已指派)、進行尋找其它分組,如果page恰好、則直接使用、中斷檢索,如果page比需要的大、表示可用,但不是最優的,會接著尋找其它chunk直到最後比較出來一個最優的(可最大程度利用page的) 2)找到合適的page後、就設定相應的page資訊、即free_map和map資訊然後返回page地址 3. small分配:
會先檢查相應規格的記憶體是否已指派、若未分配或者分配已使用完、則申請相應頁數的page、page的分配過程與large分配一致,申請到page之後,按照固定大小切割成slot、slot之間用單鏈表串連,鏈表頭部儲存至AG(mm_heap)->free_slot
記憶體池釋放的粒度是chunk,通過efree來完成1. huge記憶體的釋放、large、smal類型的因為chunk的第一個page被佔用、所以不可能是相對chunk的位移量為0,由此區分chunk類型和large、small類型,釋放時,之間將佔用的chunk釋放、同時從AG鏈表刪除2. large記憶體的釋放:若計算得到offset不為0,表示該地址是large或者small記憶體、然後根據offset算出是第幾個page、得到page之後可以從chunk->map中得到page的配置類型、就可以釋放指定類型的記憶體了,large並不會直接釋放、而是將page的分配資訊置為未分配、若釋放後、發現該chunk下都是未分配的,則釋放chunk、釋放時優選選擇把chunk移到AG,緩衝數達到一定值後就不再繼續緩衝新加入的chunk、將記憶體歸還系統、避免佔用過多的記憶體,在分配chunk時若發現chached_chunks中有緩衝的chunk直接取出使用、不向系統發出申請3. small類型的釋放、直接將釋放的slot插回該規則slot的可用鏈表的頭部即可、比較簡單。