首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PHP内存池分析

PHP内存池分析

作者头像
心平气和
发布2020-09-11 11:29:04
1.2K0
发布2020-09-11 11:29:04
举报

一、为什么需要内存池

内存是非常宝贵的资源,需要最优访问;

操作系统适合管理大块内存,如一页(4096字节),不适合小块内存分配;不做内存池管理,容易产生内存碎片,会出现剩余内存够,但没有一块连续内存来分配,会引起操作系统把程序HOLD住来整理碎片的情况;

另外直接调用操作系统分配内存会导致从用户态切换到内核态,开销比较大;

二、内存池设计目标:

1、化零为整,减少系统调用;

2、不出现内存泄露;

3、高效,尽量无锁设计;

三、PHP内存池实现

这是官方的示意图,其中free_buckets代表小块内存列表,large_free_buckets代表大块内存列表,还有一个rest_buckets, 鸟哥的解释是:"这个结构是个双向列表, 用来保存一些PHP分配后剩下的内存, 避免无意义的把剩余内存插入free_buckets带来的性能问题"。

对于小块内存, PHP还引入了cache机制:

引入cache机制希望做到,一次定位就能查找分配。

其中free_bitmap和和large_free_bitmap为位图,指示对应位相应的内存索引是否有空闲内存。

下面会具体说明PHP是如何管理内存,在说明之前先说明下环境,笔记实验的机器是64位的,下面的数据都是基于这个前提。

PHP内存管理主要是围绕free_buckets和large_free_buckets这二个数组来 展开的,这二个数组都是一个长度为64的数组。

1、小块内存管理

free_buckets管理长度小于等于528字节的内存,free_buckets[0]管理长度为长度16-23字节的内存,free_buckets[1]管理长度为24-31字节的内存,依此类推……

其中每一项又是一个双向链表,讲起来比较抽象,我们来画图描述下分配和释放内存后内存的布局吧。

刚开始free_buckets数组每项都为NULL:

归还32字节内存后

归还36字节内存后

下次假设要分配长度32-39字节之间的内存如35,直接从下标2中遍历元素,只要哪个元素的长度大于等于要分配的长度,即将长度为36的内存归还。

接下来我们看下小块内存的分配是怎么处理的,为了保证内存分配的高效,PHP每次会从操作系统分配大块内存,默认是256KB,可以通过环境变量ZEND_MM_SEG_SIZE来设置。

从操作系统分配内存后,PHP会根据前面的换算关系,将内存块放到相应的内存块中,便于后续快速分配。

2、大块内存管理

小块内存管理长度小于等于528(参考宏ZEND_MM_MAX_SMALL_SIZE的定义)字节的内存,大于528的都由large_free_buckets来管理,large_free_buckets也是长度为64的数组,每个下标管理的内存范围是前开闭区间,设下标为i,则管理内存长度为[2^i, 2^(i+1))。

举几个例子,large_free_buckets[9]的下标为9,2的9次方是512, 所以其管理长度为512-1023之间的内存;

large_free_buckets[10]管理长度为1024-2047之间的内存;large_free_buckets[11]管理长度为2048-4095之间的内存……

这样一共可以管理最大2^64的内存,当然实际不会用这么多,因为PHP有内存限制相关参数。

可以看到,在大块内存的设计时,并没有和小块内存一样每个下标管理的内存长度差为8,而是下一个下标管理的长度为上一个下标管理的长度的2倍;之所以这样设计,因为大块内存比较大,不用太细的管理,另外就是要尽量节省内存,如果相邻下标管理内存长度差为8字节,则需要很大的数组来管理这些内存。

这样设计还会有个问题,可能会造成巨大的内存浪费,如下标10管理1024-2047之间的内存,如果释放一块长度为2046的内存,但申请的时候只要1030字节,则多余的1016字节就白白浪费了,对于这个问题,PHP通过树和双向列表来管理:

什么意思呢,结合释放内存的过程说明下:

1)、释放2048字节内存

结合前面讲的,落在下标的11元素上,2048的二进制是100000000000,其中第1个1表示落在哪个下标中,这里从右到左数排第12位,从0开始计算就是第11位;

从左到右数第二位是0,所以放到下标1这个元素的左子树上。

2、释放3100字节内存

3100的二进制是110000011100,从左到右数第二位是1,所以放在右子树上。

3、释放4093字节内存

4093的二进制是111111111101,从左到右第二位是1,放右子树上,发现右子树已经有了3100,再往右数,第三位还是1,所以放到3100的右子树上

申请的时候就是扫这个顺序扫描的,如果对应二进制位为0,则扫描左子树,如果为1则扫描右子树。

举个例子,这时如果要申请2900字节内存,转成二进制为101101010100,从左往右第2位为1,所以走到3100这里就返回了,而不会分配到4093字节的内存。

四、总结

1、PHP的内存分配主要是围绕两个数组来展开:free_buckets和large_free_buckets,其中前者用来管理小块内存,后者用来管理大块内存。

2、对于小块内存,做到尽量可以再次使用,分成64个区段,每段管理的内存字节间隔为8,即下标为0管理16-23,下标1管理24-31,依此类推……

3、对于大块内存,数组不宜过大,所以数组的长度也是64,但是为了不浪费内存,采用树+双向列表来管理,方便快速查找,也不会浪费太多内存。

4、内存分配时先从操作系统分配较大块内存,分配完后放入上述相应的数组中,方便下次使用。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员升级之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档