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

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

三种内存对应三种粒度:

  • 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 结构保存,三者间的关系:

分配过程:

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

c 复制代码
/* 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 结构体

c 复制代码
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 大小的内存。

释放内存

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

本文由mdnice多平台发布

相关推荐
炒空心菜菜43 分钟前
MapReduce 实现 WordCount
java·开发语言·ide·后端·spark·eclipse·mapreduce
wowocpp3 小时前
spring boot Controller 和 RestController 的区别
java·spring boot·后端
后青春期的诗go3 小时前
基于Rust语言的Rocket框架和Sqlx库开发WebAPI项目记录(二)
开发语言·后端·rust·rocket框架
freellf3 小时前
go语言学习进阶
后端·学习·golang
全栈派森5 小时前
云存储最佳实践
后端·python·程序人生·flask
CircleMouse5 小时前
基于 RedisTemplate 的分页缓存设计
java·开发语言·后端·spring·缓存
獨枭6 小时前
使用 163 邮箱实现 Spring Boot 邮箱验证码登录
java·spring boot·后端
维基框架6 小时前
Spring Boot 封装 MinIO 工具
java·spring boot·后端
秋野酱6 小时前
基于javaweb的SpringBoot酒店管理系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端
☞无能盖世♛逞何英雄☜7 小时前
Flask框架搭建
后端·python·flask