资讯专栏INFORMATION COLUMN

【PHP源码分析】small内存规格的计算

waltr / 2481人阅读

摘要:作者李德内存分配计算在源码中,有一段对内存规格的计算,具体在的函数中,其目的是传入一个,计算对应的规格。见代码可以看出,这段代码中分为两种情况进行讨论小于等于的情况大于的情况下面我们对这两种情况详细分析下。

作者:李德

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 0000

128 | 1000 0000
160 | 1010 0000
192 | 1100 0000
224 | 1110 0000

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/30163.html

相关文章

  • PHP7源码分析PHP内存管理

    摘要:分页管理先说说虚拟内存的概念。每个存在的虚拟页面都保存在某个区域中,不属于任何一个区域的虚拟页是不存在的,不能被进程使用内核为系统中的每个进程维护一个单独的任务结构,任务中的一个字段指向,他描述了虚拟内存的当前状态。 作者: 顺风车运营研发团队 李乐 第一章 从操作系统内存管理说起 程序是代码和数据的集合,进程是运行着的程序;操作系统需要为进程分配内存;进程运行完毕需要释放内存;内存管...

    waltr 评论0 收藏0
  • PHP源码学习】2019-03-07 PHP内存管理1笔记

    摘要:换句话说,尽量不要有内存碎片。例如分配的内存,不能只分配个,因为会剩下大小的内存碎片,不能够再次利用并分配。但是如果最小公倍数过大,导致所需要的太多,那么退而求其次,即使留有很少的内存碎片,也算是可以满足需求的。同时也避免了内存碎片的产生。 baiyan 全部视频:https://segmentfault.com/a/11... 源视频地址:http://replay.xesv5.co...

    shiyang6017 评论0 收藏0
  • (PHP7内核剖析-9) 内存管理

    摘要:但在多线程模式下会有多个,也就是说每个线程都有一个独立的内存池内存分配分配超过内存的申请,与通用的内存申请没有太大差别,只是将申请的内存块通过单链表进行了管理。的分配实际就是分配多个,的分配也是内存分配的基础,它是向系统申请内存的唯一粒度。 1.Zend内存池 内存池是内核中最底层的内存操作,定义了三种粒度的内存块:chunk、page、slot,每个chunk的大小为2M,page大...

    ygyooo 评论0 收藏0
  • PHP源码学习】2019-03-11 PHP内存管理3笔记

    摘要:那么问题来了,为什么要把如此简单的一个数组初始化问题复杂化这样写代码真的真的不会被别人锤吗其实源码中还有其他相关部分我们可以看到,源码中提供了三个类似的宏替换结构。 baiyan 全部视频:https://segmentfault.com/a/11... 源视频地址:http://replay.xesv5.com/ll/24... 复习 PHP内存分配流程 showImg(https:...

    wangjuntytl 评论0 收藏0
  • 鸿蒙轻内核源码分析:虚实映射

    摘要:处设置内核地址空间的映射。内核虚拟地址空间是固定映射到物理内存的。处按指定的标签对段之前的内存区间进行虚实映射。 摘要:本文介绍了MMU虚实映射的基本概念,运行机制,分析了映射初始化、映射查询、映射虚拟内存和物理内存,解除虚实映射,更改映射属性,重新映射等常用接口的代码。本文分享自华为云社区​​《使用MRS CDL实现实时...

    不知名网友 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<