PHP7内核实现原理-内存管理

应用程序通过与 Zend MM(Zend Memory Manager)交互,来间接实现对操作系统内存的使用、屏蔽掉操作系统细节,简化了开发,并提供内存池技术,性能得到大幅提升。

alt

三种内存对应三种粒度:

  • Huge(chunk): 占用 2M,一个 chunk 由 512 个 page 组成。申请内存大于 2M 时,直接调用系统分配,分配若干个 chunk
  • Large(page): 占用 4K。申请内存大于 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 上查找可用位置

面包店比喻:chunk 相当于面粉仓库,page 相当于一袋面粉,slot 相当于做好的小面包。顾客购买面包时,优先从 slot 中选择匹配的,如果不满足,则再重新和面甚至从仓库取面粉。同时有一个原则:返回满足需求 size 的最小规格,如申请 7k 内存,会返回 8k 内容,申请 30k 内存,会返回32k。

三种尺寸通过 zend_mm_heap 结构保存,三者间的关系:

alt

分配过程:

alt

在 Zend/zend_alloc_sizes.h 中定义了small 内存的 size,第一列从 0 到 29 表示一共 30 种 size,第二列从 8 字节到 3072 字节表示每种 size 的大小,第三列表示预留了多少个这种 size,第四列表示需要用多少个 page。

/* num, size, count, pages */
#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)

zend_mm_heap 结构体

struct _zend_mm_heap {
#if ZEND_MM_CUSTOM
 int                use_custom_heap;
#endif
#if ZEND_MM_STORAGE
 zend_mm_storage   *storage;
#endif
#if ZEND_MM_STAT
 size_t             size;                    /* current memory usage */
 size_t             peak;                    /* peak memory usage */
#endif
 zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */
#if ZEND_MM_STAT || ZEND_MM_LIMIT
 size_t             real_size;               /* current size of allocated pages */
#endif
#if ZEND_MM_STAT
 size_t             real_peak;               /* peak size of allocated pages */
#endif
#if ZEND_MM_LIMIT
 size_t             limit;                   /* memory limit */
 int                overflow;                /* memory overflow flag */
#endif

 zend_mm_huge_list *huge_list;               /* list of huge allocated blocks */

 zend_mm_chunk     *main_chunk;
 zend_mm_chunk     *cached_chunks;   /* list of unused chunks */
 int                chunks_count;   /* number of alocated chunks */
 int                peak_chunks_count;  /* peak number of allocated chunks for current request */
 int                cached_chunks_count;  /* number of cached chunks */
 double             avg_chunks_count;  /* average number of chunks allocated per request */
#if ZEND_MM_CUSTOM
 union {
  struct {
   void      *(*_malloc)(size_t);
   void       (*_free)(void*);
   void      *(*_realloc)(void*, size_t);
  } std;
  struct {
   void      *(*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
   void       (*_free)(void*  ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
   void      *(*_realloc)(void*, size_t  ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
  } debug;
 } custom_heap;
#endif
};

在申请 chunk 和 page 时,必须是整数倍。如 2M 4M 等,4K 8K 12K等。

small 内存分配过程

比如语句 $a = [1, 2, 3]; 用到了 31 字节,小于 3K,走 small 内存申请。

通过 zend_mm_small_size_to_bin 函数,计算应该从上面的 ZEND_MM_BINS_INFO 中取 num3 的行,即 32 字节,应该用 1 个 page。

从 chunk list 上第 1(page_num)个位置,拿出 1 个 page,分成 128 份,每份 32 字节,分配给变量后,其余的挂在 mm_heap 的 free_slot 后面。下次再有需要 32 字节尺寸的内存,直接取用即可,不用重新申请了。

内存池的内存对齐

内存对齐会浪费一点空间,但会提高读写效率。

chunk 是以最小 2M 分配的,每次分配时为保证内存对齐,申请 4M 内存地址,这样申请的空间横跨 2 个 2M,之后掐头去尾,留下中间的完整 2M 地址,实现内存对齐。又由于 mmap 管理的最小单位是页即 4K 大小,这样可以做一步优化,每次 chunk 申请时,申请 4M-4k 大小的内存。

alt

释放内存

先区分是 small、large、huge 三种中哪一种内存,不同类型所占用空间大小和起始位置不同,可以计算出具体应该释放的位置。其中 small 内存释放后,还可以挂到 free_slots 上之后再用。

本文由 mdnice 多平台发布

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值