php7 唯一数字_php7引用计数

1. 什么是引用计数

在《php7 zval及变量存储方式》的2.3节中我们说到,对于复杂类型的变量(string,array,object,resource等),我们会将其具体的值记录在单独的内存区域,再由zend_value中相应的指针指向该内存区域。指向该内存区域的指针数量,即为引用计数。

引用计数是服务于垃圾回收的机制的。当引用计数为0,相应的内存区域就可以回收了。

官方手册中有关于引用计数的阐述,不过应该是针对5.版本的,和7.相比,大体思想是一样的,但实现和使用xdebug_debug_zval()得到的结果差别非常大。

2. 如何查看引用计数

安装xdebug后,可以使用xdebug_debug_zval()查看变量的引用计数。比如:

$a = 123;

xdebug_debug_zval('a');

输出

a: (refcount=0, is_ref=0)=123

refcount表示引用计数, is_ref表示是否为引用类型。

3. 简单类型的引用计数

我们说的简单类型是指:bool(true/false), null, long,double

对于这些类型的变量值,直接使用zval结构就可以记录,无需额外的内存。所以,也就没有引用计数。

更深层的原因是,php7开始,zval是在栈空间分配的,可自动释放,不需要垃圾回收(堆上的内存才需要主动管理回收),也就不需要引用计数了。

3.1 赋值

$a = 6.2;

$b = $a;

xdebug_debug_zval('a');

xdebug_debug_zval('b');

输出

a: (refcount=0, is_ref=0)=6.2

b: (refcount=0, is_ref=0)=6.2

math?formula=b%3Da变量赋值时,新申请一个zval,使zval.value.dval=6.2即可。

3.2 引用

先来看例子:

$a = 6.2;

$b = &$a;

xdebug_debug_zval('a');

xdebug_debug_zval('b');

输出:

a: (refcount=2, is_ref=1)=6.2

b: (refcount=2, is_ref=1)=6.2

a,b都变为引用类型,引用计数都变成了2,这是为什么呢。这与php7对引用的实现有关。

3.2.1 引用类型

为了实现引用 ,php7引入了内部类型--引用类型,其结构体如下:

typedef struct _zend_reference zend_reference;

struct _zend_reference {

zend_refcounted_h gc;

zval val;

};

gc: 与垃圾回收相关

val: 一个zval结构

3.2.2 对例子的解析

对于3.1中普通赋值的例子,赋值后,

math?formula=a%2Cb是两个独立的zval结构,情形如下:

ba99748f0730

普通赋值

对于3.2中引用赋值的例子,php创建了一个引用类型的结构体,

math?formula=a%2Cb中的value.ref均指向它,情形如下:

ba99748f0730

引用赋值

所以我们看到a,b的引用计数为2,且都为引用类型。

思考:如果接下来我们执行如下代码,得到什么结果?

unset($a);

xdebug_debug_zval('b');

4. 字符串的引用计数

仍然先看例子:

$h = "time";

$i = $h;

xdebug_debug_zval('h');

xdebug_debug_zval('i');

$y = "time".time();

$x = $y;

xdebug_debug_zval('x');

xdebug_debug_zval('y');

输出

h: (interned, is_ref=0)='time'

i: (interned, is_ref=0)='time'

x: (refcount=2, is_ref=0)='time1598507697'

y: (refcount=2, is_ref=0)='time1598507697'

为什么同样是字符串,

math?formula=h%2Ci没有引用计数。而

math?formula=y%2Cx的引用计数为2呢?

4.1 字符串的类型

zend_types.h中做了如下定义,注意,这个类型并不是记录在zval.u1.v.type中的,而是记录在zval.value->gc.u.flags中,主要服务于垃圾回收的。

/* string flags (zval.value->gc.u.flags) */

#define IS_STR_INTERNED GC_IMMUTABLE /* interned string */

#define IS_STR_PERSISTENT GC_PERSISTENT /* allocated using malloc */

#define IS_STR_PERMANENT (1<<8) /* relives request boundary */

具体的字串类别见下图(参考《php7底层设计与源码实现》4.3.2 字符串的类别)

ba99748f0730

字符串分类

其中,内部字串和已知字串,都会存在于php运行的整个周期,不涉及垃圾回收问题,自然也不需要引用计数。

临时字串,只能在虚拟机执行opcode时计算出来并动态分配内存存储,需要引用计数。

4.2 对例子的解释

$h = "time";

$i = $h;

time就属于字串字面量,所以得到

h: (interned, is_ref=0)='time'

i: (interned, is_ref=0)='time'

$y = "time".time();

$x = $y;

time()只能在运行时计算,所以"time".time()是临时字串。赋值后存储情况如下图所示:

ba99748f0730

临时字串赋值

因而引用计数为2。

5. 数组的引用计数

5.1 不可变数组

php7中引入了不可变数组(immutable array)的概念。一个不可变数组,是由不可变元素构成的,这些元素在编译阶段就可完全解析确定,比如string, integer, float等等。引入这种类型主要是为了优化内存。对于不可变数组,规定其初始引用计数为2.

下面看一个例子:

$a = [1,2.1,'x'];

xdebug_debug_zval('a');

$b = $a;

xdebug_debug_zval('a');

xdebug_debug_zval('b');

输出结果:

a: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=2.1, 2 => (interned, is_ref=0)='x')

a: (refcount=3, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=2.1, 2 => (interned, is_ref=0)='x')

b: (refcount=3, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=2.1, 2 => (interned, is_ref=0)='x')

说明:

$a为不可变数组,所以引用计数为2

math?formula=b%3Da赋值后,两者的zval.value.arr指向同一个zend_array, 引用计数+1, 所以a,b的引用计数都为3。

5.2 普通数组

如何产生一个普通数组(非不可变数组)呢?

动态生成数组

对不可变数组做任何改变(增减元素,改变元素值)

看下面的例子

$c = range(1,2);

xdebug_debug_zval('c');

$j = $c;

xdebug_debug_zval('j');

xdebug_debug_zval('c');

输出:

c: (refcount=1, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=2)

j: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=2)

c: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=2)

$c是一个普通数组,引用计数为1。

math?formula=j%3Dc后,引用计数+1,所以两者引用计数均变为2。

接着我们看一个复杂些的例子。

$a = ['y', 'x'];

xdebug_debug_zval('a');

print("after b=a\n");

$b = $a;

xdebug_debug_zval('a');

xdebug_debug_zval('b');

print("after change a[0]\n");

$a[0] = 'b';

xdebug_debug_zval('a');

xdebug_debug_zval('b');

输出

a: (refcount=2, is_ref=0)=array (0 => (interned, is_ref=0)='y', 1 => (interned, is_ref=0)='x')

after b=a

a: (refcount=3, is_ref=0)=array (0 => (interned, is_ref=0)='y', 1 => (interned, is_ref=0)='x')

b: (refcount=3, is_ref=0)=array (0 => (interned, is_ref=0)='y', 1 => (interned, is_ref=0)='x')

after change a[0]

a: (refcount=1, is_ref=0)=array (0 => (interned, is_ref=0)='b', 1 => (interned, is_ref=0)='x')

b: (refcount=2, is_ref=0)=array (0 => (interned, is_ref=0)='y', 1 => (interned, is_ref=0)='x')

说明:

起初a为不可变数组,引用计数为2

math?formula=b%3Da后,引用计数+1,故为3

改变

math?formula=a%5B0%5D%E5%90%8E%EF%BC%88**%E6%B3%A8%E6%84%8F%EF%BC%8C%E6%AD%A4%E5%A4%84%E5%8F%91%E7%94%9F%E4%BA%86cow~%E5%86%99%E6%97%B6%E6%8B%B7%E8%B4%9D%EF%BC%8C%E5%8D%B3a完全复制之前的数组,再修改第0个元素**),

math?formula=a%E6%88%90%E4%B8%BA%E6%99%AE%E9%80%9A%E6%95%B0%E7%BB%84%EF%BC%8C%E5%BC%95%E7%94%A8%E8%AE%A1%E6%95%B0%E4%B8%BA1%E3%80%82b指向原有的不可变数组,引用计数为2。

6. 类的引用计数

class Demo{

public $name = "ball";

}

$a = new Demo();

$b = $a;

结果

a: (refcount=2, is_ref=0)=class Demo { public $name = (refcount=0, is_ref=0)='ball' }

b: (refcount=2, is_ref=0)=class Demo { public $name = (refcount=0, is_ref=0)='ball' }

math?formula=a%2Cb的zval.value.obj指向同一个zend_object, 因此引用计数为2,比较好理解。

7. 总结

变量是简单类型(true/false/double/long/null)时直接拷贝值,不需要引用计数。

变量是临时字串,在赋值时会用到引用计数,但如果变量是字符常量(字串字面量),则不会用到。

变量是对象(zval.u1.v.type=IS_OBJECT), 资源(zval.u1.v.type=IS_RESOURCE), 引用(zval.u1.v.type=IS_REFERENCE)类型时, 赋值一定会用到引用计数。

变量是普通的数组, 赋值时会用到引用计数,变量是IS_ARRAY_IMMUTABLE时,赋值不使用引用计数。

| type | refcounted |

+----------------+------------+

|simple types | |

|string | Y |

|interned string | |

|array | Y |

|immutable array | |

|object | Y |

|resource | Y |

|reference | Y |

当前变量若支持引用计数, 则zval.u1.v.type_flag包含 (注意是&,不是等于)IS_TYPE_REFCOUNTED

#define IS_TYPE_REFCOUNTED (1<<2)

8. 引用计数记录在哪里

php7将引用计数记录在具体的类型结构体中。

来看下Zend/zend_types.h中对string,array,object,resource,reference种支持引用计数的类型的定义:

struct _zend_string {

zend_refcounted_h gc;

zend_ulong h; /* hash value */

size_t len;

char val[1];

};

struct _zend_array {

zend_refcounted_h gc;

... ...

};

struct _zend_object {

zend_refcounted_h gc;

uint32_t handle; // TODO: may be removed ???

zend_class_entry *ce;

const zend_object_handlers *handlers;

HashTable *properties;

zval properties_table[1];

};

struct _zend_resource {

zend_refcounted_h gc;

int handle; // TODO: may be removed ???

int type;

void *ptr;

};

struct _zend_reference {

zend_refcounted_h gc;

zval val;

zend_property_info_source_list sources;

};

每种类型中都包含

zend_refcounted_h gc

这是一个专门服务于垃圾回收的结构,定义如下:

typedef struct _zend_refcounted_h {

uint32_t refcount; /* reference counter 32-bit */

union {

uint32_t type_info;

} u;

} zend_refcounted_h;

其中的refcount,就是用来记录引用计数的具体数值的。

9. 参考资料

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值