【PHP源码分析】small内存规格的计算
作者:李德
small内存分配计算bin_num
在PHP源码中,有一段对small内存规格的计算,具体在Zend/zend_alloc.c的zend_mm_small_size_to_bin函数中,其目的是传入一个size,计算对应的规格。见代码:
if (size <= 64) {/* we need to support size == 0 ... */return (size - !!size) >> 3;
} else {t1 = size - 1;t2 = zend_mm_small_size_to_bit(t1) - 3;t1 = t1 >> t2;t2 = t2 - 3;t2 = t2 << 2;return (int)(t1 + t2);
}
可以看出,这段代码中分为两种情况进行讨论:
- 1、size小于等于64的情况;
- 2、size大于64的情况;
下面我们对这两种情况详细分析下。
对于size小于等于64的情况
- 看
ZEND_MM_BINS_INFO
这个宏知道当size小于等于64的情况是一个等差数列,递增8,所以使用size除以8就行(源码中是右移3位)size >> 3
- 但是要考虑到size等于8、16等的情况,所以为
(size - 1) >> 3
- 然后要考虑到为0的情况,所以源码中对于
-1
的处理是!!size
,当size为0的情况!!0 = 0
。所以当size为0的情况就把-1
转换成了-0
,最终有了源码中的表达式(size - !!size) >> 3
对于size大于64的情况
t1 = size - 1;
t2 = zend_mm_small_size_to_bit(t1) - 3;
t1 = t1 >> t2;
t2 = t2 - 3;
t2 = t2 << 2;
return (int)(t1 + t2);
初始懵逼
- 初看这个代码,容易一脸懵逼,这些t1 t2 都是啥啊
- 不过不用怕,我们一点点来分析
步骤分析
/* 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)#endif /* ZEND_ALLOC_SIZES_H */
size = size - 1;
这个是边界情况,跟前面一样,后面出现的size暂且都认为已近减一了- 假设不看这个源码,我们要实现在
ZEND_MM_BINS_INFO
中找到对应的bin_num - 由
ZEND_MM_BINS_INFO
得知后续的增加4个为一组,分别为
2^4, 2^5, 2^6...
有了这个分组信息的话,我们要找siez对应的bin_num
- 找到这个size属于哪一组
- 并且size在组内的偏移是多少
- 计算组的起始位置
- 那现在问题转换成了上面3个小问题,我们一个一个来解决
找到size属于哪一组
- 最简单的办法就是比大小是吧,可以使用if...else 来一个一个比,但是显然php源码不是这样干的,那我们还有什么其它的办法呢?
- 我们看十进制看不出来什么名堂,就把这些值转成二进制看看吧
64 | 100 0000
80 | 101 0000
96 | 110 0000
112 | 111 0000128 | 1000 0000
160 | 1010 0000
192 | 1100 0000
224 | 1110 0000256 | 1 0000 0000
320 | 1 0100 0000
384 | 1 1000 0000
448 | 1 1100 0000.....
- 我们看下上面的二进制,会发现每组内的二进制长度相等,并且后面每个都比前面多一位
- 那就是说我们可以计算二进制的长度来决定它的分组,那么二进制的长度又是啥呢,其实就是当前二进制的最高位为
1
的位数 - 那么问题又转换成了求二进制中最高位的
1
的位数 - 下面给出php源码的解法,这里暂时不对其解析,只要知道它返回的是二进制中最高位的
1
的位数
int n = 16;
if (size <= 0x00ff) {n -= 8; size = size << 8;}
if (size <= 0x0fff) {n -= 4; size = size << 4;}
if (size <= 0x3fff) {n -= 2; size = size << 2;}
if (size <= 0x7fff) {n -= 1;}
return n;
- 假设我们申请的size为65,那么这里的n返回7
计算size在组内的偏移量
- 这个简单,直接用size减去每组的起始siez大小然后除以当前组内的差值(16、32、64...)即可,也就是
(size-64)/16 (size-128)/32 (size-256)/64
- 现在来看看上一步中的返回的值,每个组分别是
7、8、9...
,那么我们现在来看看这样的数据怎么计算组内的偏移量
(size - 2^4 * 4) / 16 = size / 2^4 - 4(size - 2^5 * 4) / 32 = size / 2^5 - 4 (size - 2^6 * 4) / 64 = szie / 2^6 - 4
- 那是不是可以用
7、8、9
减去3
得到4、5、6
,这样我们就可以根据它在哪一组的信息得到当前组的差值(16、32、64...) - 当size为65时,偏移量是不是就是
(65-64) / 2^4 = 0
计算组的起始位置
- 现在我们有了偏移量的信息,假定我们分组是1、2、3
- 那是不是就是用最高位的
1
的位数减去6
就可以得到分组信息了 - 得到分组信息之后,怎么知道每组的起始位置呢
- 我们知道起始位置分别是
8、12、16...
它也是一个等差数列,就是4n+4
我们在看看size=65的那个例子
- 计算的偏移量是0
- 计算的起始位置是
4*1 + 4 = 8
- 所以当size=65的bin_num就是起始位置加上偏移量
8 + 0 = 8
我们再看一个size=129的例子
偏移量是
- 二进制中最高位的
1
的位数为8 - 然后用8减去3得到5
(129 - 1 - 32 * 4) / 64 = 0
- 二进制中最高位的
- 计算起始位置是
4 * 2 + 4 = 12
- 两者相加就是
12 + 0 = 0
size=193
偏移量是
- 二进制中最高位的
1
的位数为8 (193 - 1 - 32 * 4) / 64 = 2
- 二进制中最高位的
- 计算起始位置是
4 * 2 + 4 = 12
- 两者相加就是
12 + 2 = 14
size=1793
偏移量是
- 二进制中最高位的
1
的位数为11 (1793 - 1 - 256 * 4) / 256 = 3
- 二进制中最高位的
- 计算起始位置是
4 * 5 + 4 = 24
- 两者相加就是
24 + 3 = 27
代码分析
php实现代码
1 t1 = size - 1;
2 t2 = zend_mm_small_size_to_bit(t1) - 3;
3 t1 = t1 >> t2;
4 t2 = t2 - 3;
5 t2 = t2 << 2;
6 return (int)(t1 + t2);
第一行
t1 = size - 1;
- 是为了考虑size为64、128...这些边界情况
第二行
t2 = zend_mm_small_size_to_bit(t1) - 3;
- 这里调用了
zend_mm_small_size_to_bit
这个函数,我们看看这个函数
/* higher set bit number (0->N/A, 1->1, 2->2, 4->3, 8->4, 127->7, 128->8 etc) */int n = 16;
if (size <= 0x00ff) {n -= 8; size = size << 8;}
if (size <= 0x0fff) {n -= 4; size = size << 4;}
if (size <= 0x3fff) {n -= 2; size = size << 2;}
if (size <= 0x7fff) {n -= 1;}
return n;
- 看注释我们就知道这个函数是用来返回当前size二进制中最高位1的位数,具体的做法呢其实就是二分法
我们通过
zend_mm_small_size_to_bit
这个函数获取了size二进制中最高位1的位数,那么这个-3
是什么神奇的操作呢- 上问的分析中提到,我们计算size在组内的偏移量的公式
(size - 2^4 * 4) / 16 = size / 2^4 - 4 (size - 2^5 * 4) / 32 = size / 2^5 - 4 (size - 2^6 * 4) / 64 = szie / 2^6 - 4
- 这里获取二进制的位数是7、8、9...通过
-3
的操作来获取相应的 4、5、6...
第三行
t1 = t1 >> t2;
- 把t1右移t2位,这又是什么神奇的操作?
- 这里我们把最后计算bin_num的数学公式给写出来,它是等于每组的起始位置加上组内的偏移量
binnum = (4n + 4) + (size / 2^n - 4)binnum = 4n + size / 2^n
- 所以第三行的意思我们就知道了,就是size右移2^n次方为
第四行
t2 = t2 - 3;
- 这个好理解,可以参照上文得到每组的起始位置的方法
第五行
t2 = t2 << 2;
- 我们再看看bin_num的计算公式
binnum = (4n + 4) + (size / 2^n - 4)binnum = 4n + size / 2^n
- 那么这行就好理解了,就是计算每组的起始位置
4n
对吧,左移两位就是乘以4
第六行
return (int)(t1 + t2);
- 这行没啥说的,就是返回了一个int类型的bin_num
【PHP源码分析】small内存规格的计算相关推荐
- caffe源码分析--SyncedMemory 内存管理机制
caffe源码分析–SyncedMemory 内存管理机制 SyncedMemory 是caffe中用来管理内存分配和CPU.GPU数据及同步的类,只服务于Blob类.SyncedMemory 对 ...
- nginx源码分析之内存池与线程池丨nginx的多进程网络实现
nginx源码分析之内存池与线程池 1. nginx的使用场景 2. nginx源码 内存池,线程池,日志 3. nginx的多进程网络实现 视频讲解如下,点击观看: [Linux后台开发系统]ngi ...
- FFMPEG4.1源码分析之 内存管理APIs av_malloc() av_mallocz()
1 av_malloc() av_malloc() 声明: 所属库:libavutil,该库是ffmpeg的功能库,提供了线程,内存,文件,加密等功能 头文件:libavutil/mem.h 该函数 ...
- Nginx学习笔记(五) 源码分析内存模块内存对齐
Nginx源码分析&内存模块 今天总结了下C语言的内存分配问题,那么就看看Nginx的内存分配相关模型的具体实现.还有内存对齐的内容~~不懂的可以看看~~ src/os/unix/Ngx_al ...
- Linux内核源码分析之内存管理
本文站的角度更底层,基本都是从Linux内核出发,会更深入.所以当你都读完,然后再次审视这些功能的实现和设计时,我相信你会有种豁然开朗的感觉. 1.页 内核把物理页作为内存管理的基本单元. 尽管处理器 ...
- nginx源码分析之内存池实现原理
建议看本文档时结合nginx源码: 1.1 什么是内存池?为什么要引入内存池? 内存池实质上是接替OS进行内存管理,应用程序申请内存时不再与OS打交道,而是从内存池中申请内存或者释放内存到内存池, ...
- FFMPEG4.1源码分析之 内存管理APIs av_freep() av_free()
1. av_freep() av_freep() 声明: 所属库:libavutil(lavu),libavutil是ffmpeg的工具类库,本函数是其内存管理类库中的函数 头文件:libavutil ...
- Nginx源码分析——ngx_pool_t内存池
引言: C/C++下内存管理是让几乎每一个程序员头疼的问题,分配足够的内存.追踪内存的分配.在不需要的时候释放内存--这个任务相当复杂.而直接使用系统调用malloc/free.new/delete进 ...
- HybridDB · 源码分析 · MemoryContext 内存管理和内存异常分析
背景 最近排查和解决了几处 HybridDB for PostgreSQL 内存泄漏的BUG.觉得有一定通用性. 这期分享给大家一些实现细节和小技巧. 阿里云上的 HybridDB for Postg ...
- PHP源码分析(内存管理)
void *ptr=_emalloc(size);_efree(*ptr) //释放内存的时候只传入ptr,并没有传入释放内存大小 当我们申请一个size大小的内存的时候,我们多申请一些存起来,下次用 ...
最新文章
- js在post后台接口的时候,一行代码完成删除对象中所有值为null、undefined或为空字符串““的属性
- Mybatis复习笔记:1
- php 定义数组使用逗号,
- cisco *** 案例2
- WiFi密码都不会破译​还想考清华?​
- python map zip_Python学习笔记(九) map、zip和filter函数
- 解决Docker容器内安装chrome浏览器无法启动bug
- 两个无线AP导致的网络故障
- 【java】简介(一)
- 环境管理体系ISO14001认证常见的审核问题有哪些?
- java中null字符串与字符串长度为0的区别
- githut 的 管理 使用
- 关于PD协议的简要说明
- 美通社企业新闻汇总 | 2019.1.10 | 全球最受欢迎商旅城市上海第四,华为发布AI数据中心交换机...
- 儿童学计算机编程好处,孩子学编程的好处和坏处
- Decorator装饰者【C++实现】
- PS合成图片#ps抠图#ps视频教程入门基础学习课程小白
- 普拉纳夫:第六感技术的惊异潜力
- 数据爬取——拍信网美女图片
- spring注解详解与用法(总览)