【PHP7源碼學習】2019-03-11 PHP內存管理3筆記

baiyansegmentfault

所有視頻:https://segmentfault.com/a/11...數組

源視頻地址:http://replay.xesv5.com/ll/24...架構

複習

PHP內存分配流程

利用gdb回顧PHP內存分配流程

  • 首先在_emalloc()函數處打一個斷點
  • alloc_globals是一個全局變量,能夠利用AG宏訪問內部字段
  • 接下來觀察mm_heap中的字段:函數

    • size = 0,peak = 0,表示當前沒有使用任何內存
    • real_size = 2097152 = 2M,表示當前已經向操做系統申請了一個chunk的內存
    • 跟進main_chunk字段:性能

      • heap字段的地址與上述mm_heap字段的地址相同,這樣能夠方便快速定位到該chunk的mm_heap。注意到其地址爲0x7ffff3800040,注意倒數第二位有一個4(十進制爲64),即其起始地址並非正好2MB對齊的,而是2MB+64B。這是爲何呢?看下圖,該結構體前幾個字段正好佔用了64B,因此heap_slot字段就只能從64B的偏移量位置開始。

- next、prev字段表明chunk與chunk之間以雙向鏈表連接,當只有一個chunk結點時,next和prev指針均指向本身
   - free_pages字段爲511,說明512個page中,有1個page被使用了,剩餘511個空閒page
   - free_map字段爲8個uint64_t類型,標記每一個page是否被使用,共512bit。每一個chunk中的512個page被分紅了8組,每組64個page,正好對應free_map中的每一項,0是未使用,1是已使用
   - map字段爲512個uint32_t類型,共2KB。來標記是small內存仍是large內存,並能夠利用低位存儲bit_num或已用的page數量等信息。這裏它是一個large內存,有1個page已經被使用(存mm_heap)

  • 咱們跟着gdb走一遍,在_emalloc()以後,調用了zend_mm_alloc_heap()函數,而後判斷size的大小,這裏小於了3KB(ZEND_MM_MAX_SMALL_SIZE),因此這裏調用了zend_mm_alloc_small()函數,也有可能調用zend_mm_alloc_small_slow()函數,視狀況而定。而後計算bin_num,這裏size爲11,小於64。因此直接return (size - !!size) >> 3,打印結果爲1,根據其bin_num去bin_data_size數組中找到應該分配的size大小是16B,1個page能夠分紅256個16B,須要1個page。這樣,PHP會拿出1個16B返回給用戶,剩餘255個16B會掛在free_slot鏈表上,且數組下標爲1(下標爲0保存8B內存,下標爲1保存16B內存...),再次打印free_slot字段,觀察第1個下標已經有了元素,由此能夠驗證咱們的結論。

複雜的宏替換

  • 針對視頻中bin_data_size數組爲何會最終通過替換後,爲何最終bin_data_size數組只存了size一列數據的問題,爲了方便講解,在此對源碼示例作了簡化:
#include <stdio.h>

#define _BIN_DATA_SIZE(num, size, elements, pages, x, y) size,

#define ZEND_MM_BINS_INFO(_, x, y) \
    _( 0,    8,  512, 1, x, y) \
    _( 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)

int main() {

    int bin_data_size[] = { ZEND_MM_BINS_INFO(_BIN_DATA_SIZE, x, y) };

    for (int i = 0; i < sizeof(bin_data_size) / sizeof(bin_data_size[0]); i++) {
        printf("%d, ", bin_data_size[i]);
    }
    
    //打印結果爲:
    //{8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 640, 768, 896, 1024, 1280, 1536, 1792, 2048, 2560, 3072}
}
  • 這樣看確實很難看出來,在這個基礎上,我作了一個簡化版本:ui

    #include <stdio.h>
    
    #define _BIN_DATA_SIZE(num, size, elements, pages, x, y)  size,
    
    #define ZEND_MM_BINS_INFO(_, x, y)  _( 0,  8,  512, 1, x, y)
    
    int main() {
    
       int data[] = { ZEND_MM_BINS_INFO(_BIN_DATA_SIZE, x, y) };
    
       for (int i = 0; i < sizeof(data) / sizeof(data[0]); i++) {
           printf("%d, ", data[i]);
       }
       //打印結果爲:
       //{8, }
    }
    • 咱們利用分而治之的思想,從代碼層面簡化問題。首先觀察的順序是由外到內,先看外面這個ZEND_MM_BINS_INFO(_, x, y)宏,宏就是替換,那麼咱們將下劃線_的位置用_BIN_DATA_SIZE這個東西替換,那麼外層宏的替換結果就直接變成了_BIN_DATA_SIZE( 0, 8, 512, 1, x, y)了。而_BIN_DATA_SIZE又是一個宏,根據定義,直接替換爲 size, ,注意這裏第一個英文逗號(並不是第二個中文逗號)也是宏替換結果的一部分,這樣作是爲了湊成一個數組初始化的語法。C語言數組初始化的語法爲int a[2] = {1,2},因爲咱們簡化了問題,只用了一個數組元素,在這裏的替換結果就是8。那麼應用到上面那個複雜版本也同理,將_BIN_DATA_SIZE這個東西替換到全部30行下劃線的位置,這樣最終就構成了一個由size組成的數組。
    • 那麼問題來了,爲何要把如此簡單的一個數組初始化問題複雜化?這樣寫代碼真的真的不會被別人錘嗎?
    • 其實源碼中還有其餘相關部分:
#define _BIN_DATA_SIZE(num, size, elements, pages, x, y) size,
static const uint32_t bin_data_size[] = {
  ZEND_MM_BINS_INFO(_BIN_DATA_SIZE, x, y)
};

#define _BIN_DATA_ELEMENTS(num, size, elements, pages, x, y) elements,
static const uint32_t bin_elements[] = {
  ZEND_MM_BINS_INFO(_BIN_DATA_ELEMENTS, x, y)
};

#define _BIN_DATA_PAGES(num, size, elements, pages, x, y) pages,
static const uint32_t bin_pages[] = {
  ZEND_MM_BINS_INFO(_BIN_DATA_PAGES, x, y)
};
  • 咱們能夠看到,PHP源碼中提供了三個相似的宏替換結構。這裏的下劃線_本質上就是一個佔位符,也能夠理解爲一個函數參數,根據這個參數的不一樣,來返回不一樣的值。
  • 這個佔位符的做用,就是能夠根據你傳入的佔位符(或參數),從這個ZEND_MM_BINS_INFO這個宏中,提取出不一樣列的元素,並巧妙地構成了數組初始化語法。好比下劃線位置替換成_BIN_DATA_SIZE,那麼就是取出size這一列;若是是_BIN_DATA_ELEMENTS,就是取出elements這一列;若是是_BIN_DATA_PAGES,就是取出pages這一列。
  • 雖然這種利用了有必定技巧性的東西,在某種程度上,這樣作可能會帶來一點點的性能提高。但在實際開發過程當中,我的以爲要儘可能避免寫讓別人難以理解的、低可讀性的代碼,這樣作換來的是可維護性、可擴展性的降低,這是得不償失的。可是若是性能提高是指數這種數量級的,仍是能夠考慮採用的。其實這就是架構師經常須要作的一個不斷權衡的過程,這就須要具體場景具體代入分析了。
相關文章
相關標籤/搜索