日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
PHP原理之內(nèi)存管理中難懂的幾個(gè)點(diǎn)

PHP的內(nèi)存管理, 分為倆大部分, ***部分是PHP自身的內(nèi)存管理, 這部分主要的內(nèi)容就是引用計(jì)數(shù), 寫時(shí)復(fù)制, 等等面向應(yīng)用的層面的管理. 而第二部分就是今天我要介紹的, zend_alloc中描寫的關(guān)于PHP自身的內(nèi)存管理, 包括它是如何管理可用內(nèi)存, 如何分配內(nèi)存等.

另外, 為什么要寫這個(gè)呢, 因?yàn)橹安]有任何資料來介紹PHP內(nèi)存管理中使用的策略, 數(shù)據(jù)結(jié)構(gòu), 或者算法. 而在我們平時(shí)開發(fā)擴(kuò)展, 修復(fù)PHP的bug的時(shí)候, 卻對(duì)這一部分的知識(shí)需要有一個(gè)良好的理解. PHP開發(fā)組內(nèi)的很多朋友也對(duì)這塊不是很清楚, 所以我覺得有必要專門寫一下.

一些基本的概念, 我就不贅述了, 因?yàn)榭创a很容易能看懂, 我這里就主要介紹幾個(gè)看代碼沒那么容易看懂的點(diǎn), 為什么這么說呢, 呵呵, 我在寫文章之前, 查找了下已有的資料, 已避免重復(fù)功, 其中看到了TIPI項(xiàng)目對(duì)這部分的描述, 發(fā)現(xiàn)其中錯(cuò)誤很多. 所以, 我想這部分就是看代碼也沒那么容易看懂的點(diǎn)

目前, 英文版的介紹也在撰寫中: Zend MM

Zend Memory Manager, 以下簡(jiǎn)稱Zend MM, 是PHP中內(nèi)存管理的邏輯. 其中有一個(gè)關(guān)鍵數(shù)據(jù)結(jié)構(gòu): zend_mm_heap:

Zend MM把內(nèi)存非為小塊內(nèi)存和大塊內(nèi)存?zhèn)z種, 區(qū)別對(duì)待, 對(duì)于小塊內(nèi)存, 這部分是最最常用的, 所以追求高性能. 而對(duì)于大塊內(nèi)存, 則追求的是穩(wěn)妥, 盡量避免內(nèi)存浪費(fèi).

所以, 對(duì)于小塊內(nèi)存, PHP還引入了cache機(jī)制:

Zend MM 希望通過cache盡量做到, 一次定位就能查找分配.

而一個(gè)不容易看懂的點(diǎn)是free_buckets的申明:

Q: 為什么free_buckets數(shù)組的長(zhǎng)度是ZEND_MM_NUMBER_BUCKET個(gè)?

A: 這是因?yàn)? PHP在這處使用了一個(gè)技巧, 用一個(gè)定長(zhǎng)的數(shù)組來存儲(chǔ)ZEND_MM_NUMBER_BUCKET個(gè)zend_mm_free_block, 如上圖中紅色框所示. 對(duì)于一個(gè)沒有被使用的free_buckets的元素, 唯一有用的數(shù)據(jù)結(jié)構(gòu)就是next_free_block和prev_free_block, 所以, 為了節(jié)省內(nèi)存, PHP并沒有分配ZEND_MM_NUMBER_BUCKET * sizeof(zend_mm_free_block)大小的內(nèi)存, 而只是用了ZEND_MM_NUMBER_BUCKET * (sizeof(*next_free_block) + sizeof(*prev_free_block))大小的內(nèi)存..

我們來看ZEND_MM_SMALL_FREE_BUCKET宏的定義:

 
 
 
 
  1. #define ZEND_MM_SMALL_FREE_BUCKET(heap, index) \
  2.     (zend_mm_free_block*) ((char*)&heap->free_buckets[index * 2] + \
  3.         sizeof(zend_mm_free_block*) * 2 - \
  4.         sizeof(zend_mm_small_free_block))

之后, Zend MM 保證只會(huì)使用prev和next倆個(gè)指針, 所以不會(huì)造成內(nèi)存讀錯(cuò)..

那么, 第二個(gè)不容易看懂的點(diǎn), 就是PHP對(duì)large_free_buckets的管理, 先介紹分配(TIPI項(xiàng)目組對(duì)此部分的描述有些含糊不清):

 
 
 
 
  1. static zend_mm_free_block *zend_mm_search_large_block(zend_mm_heap *heap, size_t true_size)

large_free_buckets可以說是一個(gè)建樹和雙向列表的結(jié)合:

large_free_buckets使用一個(gè)宏來決定某個(gè)大小的內(nèi)存, 落在什么index上:

 
 
 
 
  1. #define ZEND_MM_LARGE_BUCKET_INDEX(S) zend_mm_high_bit(S)

zend_mm_high_bit獲取true_size中***位1的序號(hào)(zend_mm_high_bit), 對(duì)應(yīng)的匯編指令是bsr(此處, TIPI項(xiàng)目錯(cuò)誤的說明為: “這個(gè)hash函數(shù)用來計(jì)算size的位數(shù),返回值為size二進(jìn)碼中1的個(gè)數(shù)-1″).

也就是說, 每一個(gè)在large_free_buckets中的元素, 都保持著指向一個(gè)大小為在對(duì)應(yīng)index處為1的size的內(nèi)存塊的指針. 誒, 有點(diǎn)繞口, 舉個(gè)例子:

比如對(duì)于large_free_buckets[2], 就只會(huì)保存, 大小在0b1000到0b1111大小的內(nèi)存. 再比如: large_free_buckets[6], 就保存著大小為0b10000000到0b11111111大小的內(nèi)存的指針.

這樣, 再分配內(nèi)存的時(shí)候, Zend MM就可以快速定位到最可能適合的區(qū)域來查找. 提高性能.

而, 每一個(gè)元素又同時(shí)是一個(gè)雙向列表, 保持著同樣size的內(nèi)存塊, 而左右孩子(child[0]和child[1])分別代表著鍵值0和1, 這個(gè)鍵值是指什么呢?

我們來舉個(gè)例子, 比如我向PHP申請(qǐng)一個(gè)true_size為0b11010大小的內(nèi)存, 經(jīng)過一番步驟以后, 沒有找到合適的內(nèi)存, PHP進(jìn)入了zend_mm_search_large_block的邏輯來在large_free_buckets中尋找合適的內(nèi)存:

1. 首先, 計(jì)算true_size對(duì)應(yīng)的index, 計(jì)算方法如之前描述的ZEND_MM_LARGE_BUCKET_INDEX

2. 然后在一個(gè)位圖結(jié)構(gòu)中, 判斷是否存在一個(gè)大于true_size的可用內(nèi)存已經(jīng)存在于large_free_buckets, 如果不存在就返回:

 
 
 
 
  1. size_t bitmap = heap->large_free_bitmap >> index;
  2. if (bitmap == 0) {
  3.    return NULL;
  4. }

3. 判斷, free_buckets[index]是否存在可用的內(nèi)存:

 
 
 
 
  1. if (UNEXPECTED((bitmap & 1) != 0))

4. 如果存在, 則從free_buckets[index]開始, 尋找最合適的內(nèi)存, 步驟如下:

4.1. 從free_buckets[index]開始, 如果free_buckets[index]當(dāng)前的內(nèi)存大小和true_size相等, 則尋找結(jié)束, 成功返回.

4.2. 查看true_size對(duì)應(yīng)index后(true_size << (ZEND_MM_NUM_BUCKETS - index))的當(dāng)前***位, 如果為1. 則在free_buckets[index]->child[1]下面繼續(xù)尋找, 如果free_buckets[index]->child[1]不存在, 則跳出. 如果true_size的當(dāng)前***位為0, 則在free_buckets[index]->child[0]下面繼續(xù)尋找, 如果free_buckets[index]->child[0]不存在, 則在free_buckets[index]->child[1]下面尋找最小內(nèi)存(因?yàn)榇藭r(shí)可以保證, 在free_buckets[index]->child[1]下面的內(nèi)存都是大于true_size的)

4.3. 出發(fā)點(diǎn)變更為2中所述的child, 左移一位ture_size.

5. 如果上述邏輯并沒有找到合適的內(nèi)存, 則尋找最小的”大塊內(nèi)存”:

 
 
 
 
  1. /* Search for smallest "large" free block */
  2.   best_fit = p = heap->large_free_buckets[index + zend_mm_low_bit(bitmap)];
  3.   while ((p = p->child[p->child[0] != NULL])) {
  4.       if (ZEND_MM_FREE_BLOCK_SIZE(p) < ZEND_MM_FREE_BLOCK_SIZE(best_fit)) {
  5.           best_fit = p;
  6.       }
  7.   }

注意上面的邏輯, (p = p->child[p->child[0] != NULL]), PHP在盡量尋找最小的內(nèi)存.

為什么說, large_free_buckets是個(gè)鍵樹呢, 從上面的邏輯我們可以看出, PHP把一個(gè)size, 按照二進(jìn)制的0,1做鍵, 把內(nèi)存大小信息反應(yīng)到了鍵樹上, 方便了快速查找.

另外, 還有一個(gè)rest_buckets, 這個(gè)結(jié)構(gòu)是個(gè)雙向列表, 用來保存一些PHP分配后剩下的內(nèi)存, 避免無意義的把剩余內(nèi)存插入free_buckets帶來的性能問題(此處, TIPI項(xiàng)目錯(cuò)誤的描述為: “這是一個(gè)只有兩個(gè)元素的數(shù)組。 而我們常用的插入和查找操作是針對(duì)***個(gè)元素,即heap->rest_buckets[0]“).

作者: Laruence( )   原文地址: http://www.laruence.com/2011/11/09/2277.html


當(dāng)前題目:PHP原理之內(nèi)存管理中難懂的幾個(gè)點(diǎn)
標(biāo)題路徑:http://m.5511xx.com/article/djjscgh.html