2019-03-11 PHP内存管理3笔记

栏目: PHP · 发布时间: 5年前

内容简介:baiyan源视频地址:

2019-03-11 PHP 内存管理3笔记

baiyan

源视频地址: http://replay.xesv5.com/ll/24...

复习

PHP内存分配流程

2019-03-11 PHP内存管理3笔记

利用gdb回顾PHP内存分配流程

2019-03-11 PHP内存管理3笔记

  • 首先在_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的偏移量位置开始。

2019-03-11 PHP内存管理3笔记

- 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)

2019-03-11 PHP内存管理3笔记

  • 我们跟着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个下标已经有了元素,由此可以验证我们的结论。
  • 2019-03-11 PHP内存管理3笔记

复杂的宏替换

  • 针对视频中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}
}
  • 这样看确实很难看出来,在这个基础上,我做了一个简化版本:

    #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这一列。
  • 虽然这种利用了有一定技巧性的东西,在某种程度上,这样做可能会带来一点点的性能提升。但在实际开发过程中,个人觉得要尽量避免写让别人难以理解的、低可读性的代码,这样做换来的是可维护性、可扩展性的下降,这是得不偿失的。但是如果性能提升是指数这种数量级的,还是可以考虑采用的。其实这就是架构师常常需要做的一个不断权衡的过程,这就需要具体场景具体代入分析了。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

连线

连线

加里·沃尔夫 / 黄锫坚 / 中国铁道出版社 / 2006-7 / 29.80元

简言之,是一个人和一本杂志的传奇……   放在大环境中,是一个时代的跌宕起伏……   一直大力地向所有人推荐这本书,只是因为,故事真的很精彩,是一个伟大而疯狂的时代所造就的一个不屈不挠的斗士——路易斯·罗塞托,还有这本举世皆知的杂志——《连线》。   通过本书,中国读者会知道,新经济热潮的主角不仅是比尔·盖茨、杨致远等技术和商业能人,还有一大批新文化的传教士和吹鼓手,比如路易斯·......一起来看看 《连线》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

html转js在线工具
html转js在线工具

html转js在线工具