前面一篇文章我们聊了下PHP5的垃圾回收机制,本篇文章再来聊下PHP7的垃圾回收机制。PHP7的速度比PHP5快了2~3倍,性能提升了不少,了解一些底层一点的东西有助于我们理解它的性能为什么会提示几倍的原因。同样的,我们要了解PHP7的垃圾回收机制,必须先得了解zval 结构。下面的源码版本是php7.2.31。
一、 几个重要的数据结构
- zval 结构
typedef struct _zval_struct zval;
struct _zval_struct {
zend_value value; /* value */
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, /*变量的类型 */
zend_uchar type_flags, /*变量类型特有的标记*/
zend_uchar const_flags, /* 常量类型的标记*/
zend_uchar reserved) /*保留字段 */
} v;
uint32_t type_info;
} u1;
union {
uint32_t next; /* 用于解决hash冲突,有冲突的key的value放入到一个链表中 */
uint32_t cache_slot; /* 缓存槽(用于运行时缓存) */
uint32_t lineno; /* 标记该变量对应php行号,用于AST(抽象语法树)节点*/
uint32_t num_args; /* 函数调用时的传参个数 */
uint32_t fe_pos; /* foreach循环位置,循环每移动一次,该值加1 */
uint32_t fe_iter_idx; /* foreach循环中的游标位置 */
uint32_t access_flags; /* 类常量访问标志 */
uint32_t property_guard; /* 防止类中的魔术方法的循环引用,get和set中会用到*/
uint32_t extra; /* 用于扩展的字段,暂时还没有明确的说明 */
} u2;
};
- zend_value 结构
typedef union _zend_value {
zend_long lval;//整形
double dval;//浮点型
zend_refcounted *counted;//获取不同类型的gc头部
zend_string *str;//string字符串
zend_array *arr;//数组
zend_object *obj;//对象
zend_resource *res;//资源
zend_reference *ref;//是否是引用类型
zend_ast_ref *ast; //抽象语法树
zval *zv; // zval类型
void *ptr; //指针
zend_class_entry *ce; // 类
zend_function *func; //函数
struct {
ZEND_ENDIAN_LOHI(
uint32_t w1,
uint32_t w2)
} ww;
} zend_value;
- zend_refcounted 引用计数结构
struct _zend_refcounted {
zend_refcounted_h gc;
};
typedef struct _zend_refcounted_h {
uint32_t refcount; /* reference counter 32-bit */
union {
struct {
ZEND_ENDIAN_LOHI_3(
zend_uchar type,
zend_uchar flags, /* used for strings & objects */
uint16_t gc_info) /* keeps GC root number (or 0) and color */
} v;
uint32_t type_info;
} u;
} zend_refcounted_h;
- zend_reference结构(是否为引用标志)
struct _zend_reference {
zend_refcounted_h gc;
zval val;
};
二、 几个重要的变量
下面先来介绍下zval,zend_value 中几个重要的变量
(1)在zval中 zend_uchar type 这个是用来表示当前变量是什么类型的变量,php7中的zval的类型做了比较大的调整,总体来说有如下19种类型:
/* regular data types */
#define IS_UNDEF 0
#define IS_NULL 1
#define IS_FALSE 2
#define IS_TRUE 3
#define IS_LONG 4
#define IS_DOUBLE 5
#define IS_STRING 6
#define IS_ARRAY 7
#define IS_OBJECT 8
#define IS_RESOURCE 9
#define IS_REFERENCE 10
/* constant expressions */
#define IS_CONSTANT 11
#define IS_CONSTANT_AST 12
/* fake types */
#define _IS_BOOL 13
#define IS_CALLABLE 14
#define IS_ITERABLE 19
#define IS_VOID 18
/* internal types */
#define IS_INDIRECT 15
#define IS_PTR 17
#define _IS_ERROR 20
其中php5的时候is_bool类型,现在拆分成了is_false和is_true两种类型,而原来的引用是一个标志位,现在的引用是一种新的类型。
对于IS_INDIRECT和IS_PTR来说,这两个类型是用在内部的保留类型,用户不会感知。
(2)在zend_value 中 zend_refcounted *counted; 表示引用计数的次数,就是zend_value变量被zval引用的次数,下面举几个例子:
$a = 123;
xdebug_debug_zval('a');
php7 输出结果:
a: (refcount=0, is_ref=0)=123
php5输出结果:
a: (refcount=1, is_ref=0)=123
分析:首先$a不是引用变量,所以is_ref都是0,不同的是在php7中refcount=0,而在php5中refcount=1,由于变量a是一个整型变量,在php中变量a的值被直接保存在了zend_value.lval字段里面,并且在php7中refcount是在zend_value结构里面的,所以在php7中不需要引用计数;在php5中refcount字段是放在zval结构里面的,zend_value被zval引用一次,所以这里是1。
三、php7 变量的内部实现
php7中变量的实现分以下几种方式
(1) 对于boolean类型,还有null,undefined,这种没有具体值,只有类型的的类型,直接在zval中通过zend_uchar type的类型来判断,无需通过引用计数来实现。

正是因为没有通过引用计数来实现,所以它refcount为0。比如:
$a = true;
xdebug_debug_zval('a');
$b= $a;
xdebug_debug_zval('b');
$c=$a;
xdebug_debug_zval('c');
输出结果:
a: (refcount=0, is_ref=0)=TRUE
b: (refcount=0, is_ref=0)=TRUE
c: (refcount=0, is_ref=0)=TRUE
我们可以看到这三个变量的refcount的都是0,
(2) 对于int类型和float类型,因为在zend_value中有zend_long和double来保存数据,所以在赋值的时候就不需要再使用引用计数了,在拷贝的时候直接进行赋值就行了,这样做可以省掉大量的引用计数的相关操作。
typedef union _zend_value {
zend_long lval;//整形
double dval;//浮点型
....
定义一个$a=1,在php内核中zval和zval_value的关系:

比如:
$a = 1;
xdebug_debug_zval('a');
$b=$a;
xdebug_debug_zval('b');
$c=$a;
xdebug_debug_zval('c');
输出结果:
a: (refcount=0, is_ref=0)=1
b: (refcount=0, is_ref=0)=1
c: (refcount=0, is_ref=0)=1
也就是说在赋值的时候是直接进行赋值操作,而不是通过改变引用计数的方式,说得直接点就是php7中普赋值操作是复制出多个zval结构出来,大家可以发挥下想象吧。
(3) 第三种就是常规的使用引用计数的方式来进行变量的定义。在这些变量实现中,都是通过指针指向一个具体的数据类型。比如字符串类型:
zend_string *str;
struct _zend_string {
zend_refcount_h gc;
zend_ulong h; /*hash value*/
size_t len;
char val[1];
}
举个例子:
$a = 'aaa';
xdebug_debug_zval('a');
输出结果:
a: (interned, is_ref=0)='aaaa'
这里的interned 表示这个一个内部字符串(函数名,类名,变量名,静态字符串),对于内部字符串而言,字符串的内容是唯一不变的,它们在生存周期存在于整个请求期间,request完后会统一销毁释放,自然也就无需通过引用计数进行内存的管理。下面在来看下其他形式的字符串。
$a = 'test'.time();
xdebug_debug_zval('a');
输出结果:
a: (refcount=1, is_ref=0)='aaaa1590654681
这种类型的字符串变量就会使用到引用计数。这是一个动态的字符串,这个时候就会使用zend_value中zend_string *str 字段就会保存一个指向zend_string结构的指针:
下面来看下引用,在了解引用之前我们先来回顾下zend_reference结构:
struct _zend_reference{
zend_refcounted_h gc;
zval val;
}
当一个变量被引用时或者说是一个引用变量的时候,php会做如下操作:
- php首先会申请一个zend_reference结构
- 将zend_
$var_int_1 = 233;
$var_int_2 = &$var_int_1;
xdebug_debug_zval('var_int_1');
输出结果:
var_int_1: (refcount=2, is_ref=1)=233
未完,待续。。
网友评论