摘要:那么問題來了,為什么要把如此簡單的一個數組初始化問題復雜化這樣寫代碼真的真的不會被別人錘嗎其實源碼中還有其他相關部分我們可以看到,源碼中提供了三個類似的宏替換結構。
baiyan
全部視頻: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#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} }
這樣看確實很難看出來,在這個基礎上,我做了一個簡化版本:
#include#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這一列。
雖然這種利用了有一定技巧性的東西,在某種程度上,這樣做可能會帶來一點點的性能提升。但在實際開發過程中,個人覺得要盡量避免寫讓別人難以理解的、低可讀性的代碼,這樣做換來的是可維護性、可擴展性的下降,這是得不償失的。但是如果性能提升是指數這種數量級的,還是可以考慮采用的。其實這就是架構師常常需要做的一個不斷權衡的過程,這就需要具體場景具體代入分析了。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/31288.html
摘要:此文用于匯總跟隨陳雷老師及團隊的視頻,學習源碼過程中的思考整理與心得體會,此文會不斷更新視頻傳送門每日學習記錄使用錄像設備記錄每天的學習源碼學習源碼學習內存管理筆記源碼學習內存管理筆記源碼學習內存管理筆記源碼學習基本變量筆記 此文用于匯總跟隨陳雷老師及團隊的視頻,學習源碼過程中的思考、整理與心得體會,此文會不斷更新 視頻傳送門:【每日學習記錄】使用錄像設備記錄每天的學習 PHP7...
摘要:并且在我們日常的代碼學習中,我們會碰到過很多很多的宏定義。如果宏定義中帶有參數,而代碼中出現同樣標識時沒有參數,不視為宏。具體的解析見源碼學習內存管理筆記。 grape 全部視頻:https://segmentfault.com/a/11... 原視頻地址:http://replay.xesv5.com/ll/24... 引入 我們知道宏定義的優點有方便程序的修改,提高程序運行效率等等...
摘要:在這里使用學而思網校的錄像設備,記錄每天學習的內容閆昌李樂階段李樂李樂李樂李樂李樂李樂馬運運李樂李樂李樂源碼集群閆昌源碼閆昌源碼主從復制李樂源碼施洪寶源碼施洪寶韓天 在這里使用學而思網校的錄像設備,記錄每天學習的內容: 2019-06-24 ~ 2019-06-28 06-27 nginx by 閆昌 06-26 nginx module by 李樂 06-25 nginx http ...
閱讀 658·2021-11-23 09:51
閱讀 3258·2021-10-11 10:58
閱讀 15407·2021-09-29 09:47
閱讀 3528·2021-09-01 11:42
閱讀 1281·2019-08-29 16:43
閱讀 1832·2019-08-29 15:37
閱讀 2089·2019-08-29 12:56
閱讀 1718·2019-08-28 18:21