<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ## 5.1 Zend內存池 zend針對內存的操作封裝了一層,用于替換直接的內存操作:malloc、free等,實現了更高效率的內存利用,其實現主要參考了tcmalloc的設計。 源碼中emalloc、efree、estrdup等等就是內存池的操作。 內存池是內核中最底層的內存操作,定義了三種粒度的內存塊:chunk、page、slot,每個chunk的大小為2M,page大小為4KB,一個chunk被切割為512個page,而一個或若干個page被切割為多個slot,所以申請內存時按照不同的申請大小決定具體的分配策略: * __Huge(chunk):__ 申請內存大于2M,直接調用系統分配,分配若干個chunk * __Large(page):__ 申請內存大于3092B(3/4 page_size),小于2044KB(511 page_size),分配若干個page * __Small(slot):__ 申請內存小于等于3092B(3/4 page_size),內存池提前定義好了30種同等大小的內存(8,16,24,32,...3072),他們分配在不同的page上(不同大小的內存可能會分配在多個連續的page),申請內存時直接在對應page上查找可用位置 ### 5.1.1 基本數據結構 chunk由512個page組成,其中第一個page用于保存chunk結構,剩下的511個page用于內存分配,page主要用于Large、Small兩種內存的分配;heap是表示內存池的一個結構,它是最主要的一個結構,用于管理上面三種內存的分配,Zend中只有一個heap結構。 ```c struct _zend_mm_heap { #if ZEND_MM_STAT size_t size; //當前已用內存數 size_t peak; //內存單次申請的峰值 #endif zend_mm_free_slot *free_slot[ZEND_MM_BINS]; // 小內存分配的可用位置鏈表,ZEND_MM_BINS等于30,即此數組表示的是各種大小內存對應的鏈表頭部 ... zend_mm_huge_list *huge_list; //大內存鏈表 zend_mm_chunk *main_chunk; //指向chunk鏈表頭部 zend_mm_chunk *cached_chunks; //緩存的chunk鏈表 int chunks_count; //已分配chunk數 int peak_chunks_count; //當前request使用chunk峰值 int cached_chunks_count; //緩存的chunk數 double avg_chunks_count; //chunk使用均值,每次請求結束后會根據peak_chunks_count重新計算:(avg_chunks_count+peak_chunks_count)/2.0 } struct _zend_mm_chunk { zend_mm_heap *heap; //指向heap zend_mm_chunk *next; //指向下一個chunk zend_mm_chunk *prev; //指向上一個chunk int free_pages; //當前chunk的剩余page數 int free_tail; /* number of free pages at the end of chunk */ int num; char reserve[64 - (sizeof(void*) * 3 + sizeof(int) * 3)]; zend_mm_heap heap_slot; //heap結構,只有主chunk會用到 zend_mm_page_map free_map; //標識各page是否已分配的bitmap數組,總大小512bit,對應page總數,每個page占一個bit位 zend_mm_page_info map[ZEND_MM_PAGES]; //各page的信息:當前page使用類型(用于large分配還是small)、占用的page數等 }; //按固定大小切好的small內存槽 struct _zend_mm_free_slot { zend_mm_free_slot *next_free_slot;//此指針只有內存未分配時用到,分配后整個結構體轉為char使用 }; ``` chunk、page、slot三者的關系: ![zend_heap](https://box.kancloud.cn/b900fcd1a5bf6192f228cbc5560519a0_914x479.png) 接下來看下內存池的初始化以及三種內存分配的過程。 ### 5.1.2 內存池初始化 內存池在php_module_startup階段初始化,start_memory_manager(): ```c ZEND_API void start_memory_manager(void) { #ifdef ZTS ts_allocate_id(&alloc_globals_id, sizeof(zend_alloc_globals), (ts_allocate_ctor) alloc_globals_ctor, (ts_allocate_dtor) alloc_globals_dtor); #else alloc_globals_ctor(&alloc_globals); #endif } static void alloc_globals_ctor(zend_alloc_globals *alloc_globals) { #ifdef MAP_HUGETLB tmp = getenv("USE_ZEND_ALLOC_HUGE_PAGES"); if (tmp && zend_atoi(tmp, 0)) { zend_mm_use_huge_pages = 1; } #endif ZEND_TSRMLS_CACHE_UPDATE(); alloc_globals->mm_heap = zend_mm_init(); } ``` __alloc_globals__ 是一個全局變量,即 __AG宏__ ,它只有一個成員:mm_heap,保存著整個內存池的信息,所有內存的分配都是基于這個值,多線程模式下(ZTS)會有多個heap,也就是說每個線程都有一個獨立的內存池,看下它的初始化: ```c static zend_mm_heap *zend_mm_init(void) { //向系統申請2M大小的chunk zend_mm_chunk *chunk = (zend_mm_chunk*)zend_mm_chunk_alloc_int(ZEND_MM_CHUNK_SIZE, ZEND_MM_CHUNK_SIZE); zend_mm_heap *heap; heap = &chunk->heap_slot; //heap結構實際是主chunk嵌入的一個結構,后面再分配chunk的heap_slot不再使用 chunk->heap = heap; chunk->next = chunk; chunk->prev = chunk; chunk->free_pages = ZEND_MM_PAGES - ZEND_MM_FIRST_PAGE; //剩余可用page數 chunk->free_tail = ZEND_MM_FIRST_PAGE; chunk->num = 0; chunk->free_map[0] = (Z_L(1) << ZEND_MM_FIRST_PAGE) - 1; //將第一個page的bit分配標識位設置為1 chunk->map[0] = ZEND_MM_LRUN(ZEND_MM_FIRST_PAGE); //第一個page的類型為ZEND_MM_IS_LRUN,即large內存 heap->main_chunk = chunk; //指向主chunk heap->cached_chunks = NULL; //緩存chunk鏈表 heap->chunks_count = 1; //已分配chunk數 heap->peak_chunks_count = 1; heap->cached_chunks_count = 0; heap->avg_chunks_count = 1.0; ... heap->huge_list = NULL; //huge內存鏈表 return heap; } ``` 這里分配了主chunk,只有第一個chunk的heap會用到,后面分配的chunk不再用到heap,初始化完的結構如下圖: ![chunk_init](https://box.kancloud.cn/48ae1ab17ccb7e4259a3998d2c6b8fc1_882x734.png) 初始化的過程實際只是分配了一個主chunk,這里并沒有看到開始提到的小內存slot切割,下一節我們來詳細看下各種內存的分配過程。 ### 5.1.3 內存分配 文章開頭已經簡單提過Zend內存分配器按照申請內存的大小有三種不同的實現: ![alloc_all](https://box.kancloud.cn/5ef5e291b79b0794321abf7ebf012a2a_554x272.png) #### 5.1.3.1 Huge分配 超過2M內存的申請,與通用的內存申請沒有太大差別,只是將申請的內存塊通過單鏈表進行了管理。 ```c static void *zend_mm_alloc_huge(zend_mm_heap *heap, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { size_t new_size = ZEND_MM_ALIGNED_SIZE_EX(size, REAL_PAGE_SIZE); //按頁大小重置實際要分配的內存 #if ZEND_MM_LIMIT //如果有內存使用限制則check是否已達上限,達到的話進行zend_mm_gc清理后再檢查 //此過程不再展開分析 #endif //分配chunk ptr = zend_mm_chunk_alloc(heap, new_size, ZEND_MM_CHUNK_SIZE); if (UNEXPECTED(ptr == NULL)) { //清理后再嘗試分配一次 if (zend_mm_gc(heap) && (ptr = zend_mm_chunk_alloc(heap, new_size, ZEND_MM_CHUNK_SIZE)) != NULL) { /* pass */ } else { //申請失敗 zend_mm_safe_error(heap, "Out of memory"); return NULL; } } //將申請的內存通過zend_mm_huge_list插入到鏈表中,heap->huge_list指向的實際是zend_mm_huge_list zend_mm_add_huge_block(heap, ptr, new_size, ...); ... return ptr; } ``` huge的分配實際就是分配多個chunk,chunk的分配也是large、small內存分配的基礎,它是ZendMM向系統申請內存的唯一粒度。在申請chunk內存時有一個關鍵操作,那就是將內存地址對齊到ZEND_MM_CHUNK_SIZE,也就是說申請的chunk地址都是ZEND_MM_CHUNK_SIZE的整數倍,注意:這里說的內存對齊值并不是系統的字節對齊值,所以需要在申請后自己調整下。ZendMM的處理方法是:先按實際要申請的內存大小申請一次,如果系統分配的地址恰好是ZEND_MM_CHUNK_SIZE的整數倍那么就不需要調整了,直接返回使用;如果不是ZEND_MM_CHUNK_SIZE的整數倍,ZendMM會把這塊內存釋放掉,然后按照"實際要申請的內存大小+ZEND_MM_CHUNK_SIZE"的大小重新申請一塊內存,多申請的ZEND_MM_CHUNK_SIZE大小的內存是用來調整的,ZendMM會從系統分配的地址向后偏移到ZEND_MM_CHUNK_SIZE的整數倍位置,調整完以后會把多余的內存再釋放掉,如下圖所示,虛線部分為alignment大小的內容,灰色部分為申請的內容大小,系統返回的地址為ptr1,而實際使用的內存是從ptr2開始的。 ![](https://box.kancloud.cn/36e9557b30beb5721810246ec71fe8bc_403x173.png) 下面看下chunk的具體分配過程: ```c //size為申請內存的大小,alignment為內存對齊值,一般為ZEND_MM_CHUNK_SIZE static void *zend_mm_chunk_alloc_int(size_t size, size_t alignment) { //向系統申請size大小的內存 void *ptr = zend_mm_mmap(size); if (ptr == NULL) { return NULL; } else if (ZEND_MM_ALIGNED_OFFSET(ptr, alignment) == 0) {//判斷申請的內存是否為alignment的整數倍 //是的話直接返回 return ptr; }else{ //申請的內存不是按照alignment對齊的,注意這里的alignment并不是系統的字節對齊值 size_t offset; //將申請的內存釋放掉重新申請 zend_mm_munmap(ptr, size); //重新申請一塊內存,這里會多申請一塊內存,用于截取到alignment的整數倍,可以忽略REAL_PAGE_SIZE ptr = zend_mm_mmap(size + alignment - REAL_PAGE_SIZE); //offset為ptr距離上一個alignment對齊內存位置的大小,注意不能往前移,因為前面的內存都是分配了的 offset = ZEND_MM_ALIGNED_OFFSET(ptr, alignment); if (offset != 0) { offset = alignment - offset; zend_mm_munmap(ptr, offset); //偏移ptr,對齊到alignment ptr = (char*)ptr + offset; alignment -= offset; } if (alignment > REAL_PAGE_SIZE) { zend_mm_munmap((char*)ptr + size, alignment - REAL_PAGE_SIZE); } return ptr; } } ``` 這個過程中用到了一個宏: ```c #define ZEND_MM_ALIGNED_OFFSET(size, alignment) \ (((size_t)(size)) & ((alignment) - 1)) ``` 這個宏的作用是計算按alignment對齊的內存地址距離上一個alignment整數倍內存地址的大小,alignment必須為2的n次方,比如一段n*alignment大小的內存,ptr為其中一個位置,那么就可以通過位運算計算得到ptr所屬內存塊的offset: ![](https://box.kancloud.cn/bbf2a375f42bb873a2b2fa062f6d3458_482x150.png) 這個位運算是因為alignment為2^n,所以可以通過alignment取到最低位的位置,也就是相對上一個整數倍alignment的offset,實際如果不用運算的話可以通過:`offset = (ptr/alignment取整)*alignment - ptr`得到,這個更容易理解些。 #### 5.1.3.2 Large分配 大于3/4的page_size(4KB)且小于等于511個page_size的內存申請,也就是一個chunk的大小夠用(之所以是511個page而不是512個是因為第一個page始終被chunk結構占用),__如果申請多個page的話 分配的時候這些page都是連續的__ 。 ```c static zend_always_inline void *zend_mm_alloc_large(zend_mm_heap *heap, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { //根據size大小計算需要分配多少個page int pages_count = (int)ZEND_MM_SIZE_TO_NUM(size, ZEND_MM_PAGE_SIZE); //分配pages_count個page void *ptr = zend_mm_alloc_pages(heap, pages_count, ...); ... return ptr; } ``` 進一步看下`zend_mm_alloc_pages`,這個過程比較復雜,簡單描述的話就是從第一個chunk開始查找當前chunk下是否有pages_count個連續可用的page,有的話就停止查找,沒有的話則接著查找下一個chunk,如果直到最后一個chunk也沒找到則重新分配一個新的chunk并插入chunk鏈表,這個過程中最不好理解的一點在于如何查找pages_count個連續可用的page,這個主要根據 __chunk->free_map__ 實現的,在看具體執行過程之前我們先解釋下 __free_map__ 的作用: __我們已經知道每個chunk由512個page組成,而不管是large分配還是small分配,其分配的最小粒子都是page(small也是先分配1個或多個page然后再進行的切割),所以需要有一個數組來記錄每個page是否已經分配,free_map的作用就是標識當前chunk下各page的分配與否,比較特別的是free_map并不是512大小的數組,因為需要記錄的信息非常簡單,只需要一個bit位就夠了,所以free_map就用`長整形`的各bit位來記錄的(實際就是bitmap),不同位數的機器長整形大小不同,因此在32、64位下16或8個長整形就夠512bit了(每個byte等于8bit,長整形為4byte或8byte),當然這么做并僅僅是節省空間,更重要的作用是可以提高查詢效率__ 。 ```c typedef zend_ulong zend_mm_bitset; /* 4-byte or 8-byte integer */ #define ZEND_MM_BITSET_LEN (sizeof(zend_mm_bitset) * 8) /* 32 or 64 */ #define ZEND_MM_PAGE_MAP_LEN (ZEND_MM_PAGES / ZEND_MM_BITSET_LEN) /* 16 or 8 */ typedef zend_mm_bitset zend_mm_page_map[ZEND_MM_PAGE_MAP_LEN]; /* 64B */ ``` `heap->free_map`實際就是:__zend_ulong free_map[16 or 8]__,以 __free_map[8]__ 為例,數組中的8個數字分別表示:0-63、64-127、128-191、192-255、256-319、320-383、384-447、448-511 page的分配與否,比如當前chunk的page 0、page 2已經分配,則:`free_map[0] = 5`: ``` //5: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 ``` ![free_map](https://box.kancloud.cn/af2736ceef5e1ea419a463db15fd6417_431x358.png) 接下來看下`zend_mm_alloc_pages`的操作: ```c static void *zend_mm_alloc_pages(zend_mm_heap *heap, int pages_count ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { zend_mm_chunk *chunk = heap->main_chunk; int page_num, len; //從第一個chunk開始查找可用page while (1) { //當前chunk剩余page總數已不夠 if (UNEXPECTED(chunk->free_pages < pages_count)) { goto not_found; }else{ //查找當前chunk是否有pages_count個連續可用的page int best = -1; //已找到可用page起始頁 int best_len = ZEND_MM_PAGES; //已找到chunk的page間隙大小,這個值盡可能接近page_count int free_tail = chunk->free_tail; zend_mm_bitset *bitset = chunk->free_map; zend_mm_bitset tmp = *(bitset++); // zend_mm_bitset tmp = *bitset; bitset++ 這里是復制出的,不會影響free_map int i = 0; //下面就是查找最優page的過程,稍后詳細分析 //find best page } not_found: if (chunk->next == heap->main_chunk) { //是否已到最后一個chunk get_chunk: ... }else{ chunk = chunk->next; } } found: //找到可用page,page編號為page_num至(page_num + pages_count) /* mark run as allocated */ chunk->free_pages -= pages_count; zend_mm_bitset_set_range(chunk->free_map, page_num, pages_count); //將page_num至(page_num + pages_count)page的bit標識位設置為已分配 chunk->map[page_num] = ZEND_MM_LRUN(pages_count); //map為兩個值的組合值,首先表示當前page屬于哪種類型,其次表示包含的page頁數 if (page_num == chunk->free_tail) { chunk->free_tail = page_num + pages_count; } return ZEND_MM_PAGE_ADDR(chunk, page_num); } ``` 查找過程就是從第一個chunk開始搜索,如果當前chunk沒有合適的則進入下一chunk,如果直到最后都沒有找到則新創建一個chunk。 注意:查找page的過程并不僅僅是夠數即可,這里有一個標準是:__申請的一個或多個的page要盡可能的填滿chunk的空隙__ ,也就是說如果當前chunk有多塊內存滿足需求則會選擇最合適的那塊,而合適的標準前面提到的那個。 __最優page的檢索過程__ : * __step1:__ 首先從第一個page分組(page 0-63)開始檢查,如果當前分組無可用page(即free_map[x] = -1)則進入下一分組,直到當前分組有空閑page,然后進入step2 * __step2:__ 當前分組有可用page,首先找到第一個可用page的位置,記作page_num,接著__從page_num開始__向下找第一個已分配page的位置,記作end_page_num,這個地方需要注意,__如果當前分組剩下的page都是可用的則會進入下一分組接著搜索__,直到找到為止,這里還會借助chunk->free_tail避免無謂的查找到最后分組 * __step3:__ 根據上一步找到的page_num、end_page_num可計算得到當前可用內存塊大小為len個page,然后與申請的page頁數(page_count)比較 * __step3.1:__ 如果len=page_count則表示找到的內存塊符合申請條件且非常完美,直接從page_num開始分配page_count個page * __step3.2:__ 如果len>page_count則表示找到的內存塊符合條件且空間很充裕,暫且記錄下len、page_num,然后繼續向下搜索,如果有更合適的則用更合適的替代 * __step3.3:__ 如果len<page_count則表示當前內存塊不夠申請的大小,不符合條件,然后將這塊空間的全部page設置為已分配(這樣下一輪檢索就不會再次找到它了),接著從step1重新檢索 下面從一個例子具體看下,以64bit整形為例,假如當前page分配情況如下圖-(1)(group1全部已分配;group2中page 67-68、71-74未分配,其余都已分配;group3中除page 128-129、133已分配外其余都未分配),現在要申請3個page: ![free_map_1](https://box.kancloud.cn/60f1b6f1deb8ea6a4c3eebced2e5c1a9_753x751.png) 檢索過程: * a.首先會直接跳過group1,直接到group2檢索 * b.在group2中找到第一個可用page位置:67,然后向下找第一個不可用page位置:69,找到的可用內存塊長度為2,小于3,表示此內存塊不可用,這時會將page 67-68標識為已分配,圖-(2) * c.接著再次在group2中查找到第一個可用page位置:71,然后向下找到第一個不可用page位置:75,內存塊長度為4,大于3,表示找到一個符合的位置,雖然已經找到可用內存塊但并不"完美",先將這個并不完美的page_num及len保存到best、best_len,如果后面沒有比它更完美的就用它了,然后將page 71-74標示為已分配,圖-(3) * d.再次檢索,發現group2已無可用page,進入group3,找到可用內存位置:page 130-132,大小比c中找到的合適,所以最終返回的page就是130-132,圖-(4) page分配完成后會將free_map對應整數的bit位從page_num至(page_num+page_count)置為1,同時將chunk->map[page_num]置為`ZEND_MM_LRUN(pages_count)`,表示page_num至(page_num+page_count)這些page是被Large分配占用的。 #### 5.1.3.3 Small分配 small內存指的是小于(3/4 page_size)的內存,這些內存首先也是申請了1個或多個page,然后再將這些page按固定大小切割了,所以第一步與上一節Large分配完全相同。 small內存總共有30種固定大小的規格:8,16,24,32,40,48,56,64,80,96,112,128 ... 1792,2048,2560,3072 Byte,我們把這稱之為slot,這些slot的大小是有規律的:最小的slot大小為8byte,前8個slot__依次遞增8byte__,后面每隔4個遞增值乘以2,即`slot 0-7遞增8byte、8-11遞增16byte、12-15遞增32byte、16-19遞增32byte、20-23遞增128byte、24-27遞增256byte、28-29遞增512byte`,每種大小的slot占用的page數分別是:slot 0-15各占1個page、slot 16-29依次占5, 3, 1, 1, 5, 3, 2, 2, 5, 3, 7, 4, 5, 3個page,這些值定義在`zend_alloc_sizes.h`中: ```c /* num, size, count, pages */ #define ZEND_MM_BINS_INFO(_, x, y) \ _( 0, 8, 512, 1, x, y) \ //四個值的含義依次是:slot編號、slot大小、slot數量、占用page數 _( 1, 16, 256, 1, x, y) \ _( 2, 24, 170, 1, x, y) \ _( 3, 32, 128, 1, x, y) \ _( 4, 40, 102, 1, x, y) \ _( 5, 48, 85, 1, x, y) \ _( 6, 56, 73, 1, x, y) \ _( 7, 64, 64, 1, x, y) \ _( 8, 80, 51, 1, x, y) \ _( 9, 96, 42, 1, x, y) \ _(10, 112, 36, 1, x, y) \ _(11, 128, 32, 1, x, y) \ _(12, 160, 25, 1, x, y) \ _(13, 192, 21, 1, x, y) \ _(14, 224, 18, 1, x, y) \ _(15, 256, 16, 1, x, y) \ _(16, 320, 64, 5, x, y) \ _(17, 384, 32, 3, x, y) \ _(18, 448, 9, 1, x, y) \ _(19, 512, 8, 1, x, y) \ _(20, 640, 32, 5, x, y) \ _(21, 768, 16, 3, x, y) \ _(22, 896, 9, 2, x, y) \ _(23, 1024, 8, 2, x, y) \ _(24, 1280, 16, 5, x, y) \ _(25, 1536, 8, 3, x, y) \ _(26, 1792, 16, 7, x, y) \ _(27, 2048, 8, 4, x, y) \ _(28, 2560, 8, 5, x, y) \ _(29, 3072, 4, 3, x, y) ``` small內存的分配過程: * __step1:__ 首先根據申請內存的大小在heap->free_slot中找到對應的slot規格bin_num,如果當前slot為空則首先分配對應的page,然后將這些page內存按slot大小切割為zend_mm_free_slot單向鏈表,free_slot[bin_num]始終指向第一個可用的slot * __step2:__ 如果申請內存大小對應的的slot鏈表不為空則直接返回free_slot[bin_num],然后將free_slot[bin_num]指向下一個空閑位置free_slot[bin_num]->next_free_slot * __step3:__ 釋放內存時先將此內存的next_free_slot指向free_slot[bin_num],然后將free_slot[bin_num]指向釋放的內存,也就是將釋放的內存插到鏈表頭部 ![free_slot](https://box.kancloud.cn/44c94f375a4ad0a880dd206b37052faa_567x439.png) ### 5.1.4 系統內存分配 上面介紹了三種內存分配的過程,內存池實際只是在系統內存上面做了一些工作,盡可能減少系統內存的分配次數,接下來簡單看下系統內存的分配。 chunk、page、slot三種內存粒度中chunk的分配是直接向系統申請的,這里調用的并不是malloc(這只是glibc實現的內存操作,并不是操作系統的,zend的內存池實際跟malloc的角色相同),而是mmap: ```c static void *zend_mm_mmap(size_t size) { ... //hugepage支持 #ifdef MAP_HUGETLB if (zend_mm_use_huge_pages && size == ZEND_MM_CHUNK_SIZE) { ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_HUGETLB, -1, 0); if (ptr != MAP_FAILED) { return ptr; } } #endif ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (ptr == MAP_FAILED) { #if ZEND_MM_ERROR fprintf(stderr, "\nmmap() failed: [%d] %s\n", errno, strerror(errno)); #endif return NULL; } return ptr; } ``` HugePage的支持就是在這個地方提現的,詳細的可以看下鳥哥的這篇文章:[http://www.laruence.com/2015/10/02/3069.html](http://www.laruence.com/2015/10/02/3069.html)。 『關于Hugepage是啥,簡單的說下就是默認的內存是以4KB分頁的,而虛擬地址和內存地址是需要轉換的, 而這個轉換是要查表的,CPU為了加速這個查表過程都會內建TLB(Translation Lookaside Buffer), 顯而易見如果虛擬頁越小,表里的條目數也就越多,而TLB大小是有限的,條目數越多TLB的Cache Miss也就會越高, 所以如果我們能啟用大內存頁就能間接降低這個TLB Cache Miss』 ### 5.1.5 內存釋放 內存的釋放主要是efree操作,與三種分配一一對應,過程也比較簡單: ```c #define efree(ptr) _efree((ptr) ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) #define efree_large(ptr) _efree_large((ptr) ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) #define efree_huge(ptr) _efree_huge((ptr) ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) ZEND_API void ZEND_FASTCALL _efree(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { zend_mm_free_heap(AG(mm_heap), ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); } static zend_always_inline void zend_mm_free_heap(zend_mm_heap *heap, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { //根據內存地址及對齊值判斷內存地址偏移量是否為0,是的話只有huge情況符合,page、slot分配出的內存地>址偏移量一定是>=ZEND_MM_CHUNK_SIZE的,因為第一頁始終被chunk自身結構占用,不可能分配出去 //offset就是ptr距離當前chunk起始位置的偏移量 size_t page_offset = ZEND_MM_ALIGNED_OFFSET(ptr, ZEND_MM_CHUNK_SIZE); if (UNEXPECTED(page_offset == 0)) { if (ptr != NULL) { //釋放huge內存,從huge_list中刪除 zend_mm_free_huge(heap, ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC); } } else { //page或slot,根據chunk->map[]值判斷當前page的分配類型 //根據ptr獲取chunk的起始位置 zend_mm_chunk *chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(ptr, ZEND_MM_CHUNK_SIZE); int page_num = (int)(page_offset / ZEND_MM_PAGE_SIZE); zend_mm_page_info info = chunk->map[page_num]; ZEND_MM_CHECK(chunk->heap == heap, "zend_mm_heap corrupted"); if (EXPECTED(info & ZEND_MM_IS_SRUN)) { zend_mm_free_small(heap, ptr, ZEND_MM_SRUN_BIN_NUM(info)); //slot的釋放上一節已經介紹過,就是個普通的鏈表插入操作 } else /* if (info & ZEND_MM_IS_LRUN) */ { int pages_count = ZEND_MM_LRUN_PAGES(info); ZEND_MM_CHECK(ZEND_MM_ALIGNED_OFFSET(page_offset, ZEND_MM_PAGE_SIZE) == 0, "zend_mm_heap corrupted"); //釋放page,將free_map中的標識位設置為未分配 zend_mm_free_large(heap, chunk, page_num, pages_count); } } } ``` 釋放的內存地址可能是chunk中間的任意位置,因為chunk分配時是按照ZEND_MM_CHUNK_SIZE對齊的,也就是chunk的起始內存地址一定是ZEND_MM_CHUNK_SIZE的整數倍,所以可以根據chunk上的任意位置知道chunk的起始位置。 釋放page的過程有一個地方值得注意,如果釋放后發現當前chunk所有page都已經被釋放則可能會釋放所在chunk,還記得heap->cached_chunks嗎?內存池會維持一定的chunk數,每次釋放并不會直接銷毀而是加入到cached_chunks中,這樣下次申請chunk時直接就用了,同時為了防止占用過多內存,cached_chunks會根據每次request請求計算的chunk使用均值保證其維持在一定范圍內。 每次request請求結束會對內存池進行一次清理,檢查cache的chunk數是否超過均值,超過的話就進行清理,具體的操作:`zend_mm_shutdown`,這里不再展開。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看