Woooowen You can do anything you set your mind to, man

Php Zval浅析

什么是zval

  • 1.zval是php底层非常重要的一种数据结构.
  • 2.php变量分为3种类型:简单类型(int,string,bool),集合类型(array,object,resource),常量(const)
  • 3.php为弱类型,定义时不指明类型,运行时可以转换数据类型.就是由于zval的功劳.
  • 4.同样zval得使用能大大的节约内存的资源消耗 ####zval结构

在phpsrc/zend/zend.h中有关于zval的函数定义.

  • value:zval存储的实际的数值
  • refcount:引用计数
  • type:主要判断变量的类型,是字符,还是整数或者数组.
  • if_ref:用来判断该变量是否引用变量

并且在初始化zval的时候会直接给refcount和is_ref一个初始值,这也就是声明的变量他得refcount必定为1.

关于zval的定义代码在phpsrc/zend/zend.h文件中

typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;

struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};

zval之value

  • zvalue是用来保存实际数值的地方.在内存中划分了一块区域给它.
  • zvalue是一个union
  • union是c中得一个联合体.union可以定义多个成员,且多个成员共享一片内存块,一次只能使用一个成员,当其中一个成员被赋值时会覆盖其他成员的值.
#include
typedef union{
int a;
int b;
}Test;
int main(){
Test c;
c.a = 123;
c.b = 321;
printf("a:%d,b:%d",c.a,c.b);
}

输出结果:a:321,b:321

这是一个union的例子.union结构中变量a赋值为123,而b赋值为321.但是最后输出的结果. 可见,union可以定义多个成员变量.

多个成员变量公用一块内存.因此其他得变量值会被最后一个被赋值的变量值覆盖.关于union和struct的不同,大家自行gg,php使用union比使用struct节省很多内存,php是不是比你想的要聪明?

zval之refcount

refcount被称为引用计数.当这个变量对应内存中得值被其他变量所引用时,该值+1. 例如当$a = 1; $b = $a;那么此时refcount = 2

下面结合代码来理解

debugzvaldump()是zend自带得php方法,可以查看refcount值,但是显示的信息不够完整.

当你声明一个变量时,他得refcount是为1得.建议大家装个xdebug去查看.xdebug中提供了一系列得方法.xdebugdebugzval可以查看refcount,以及is_ref的值.

$a = 1;
debug_zval_dump('a');

输出结果:string(1) "a" refcount(1)

$a = 1;
xdebug_debug_zval('a');

输出结果:a: (refcount=1, is_ref=0)=1

当$b = $a; 变量a的refcount ++; 使用传值的时候:

$a = 1;
$b = $a;
xdebug_debug_zval('a');

输出结果:a: (refcount=2, is_ref=0)=1 使用传引用的时候:

$a = 1;
$b = &$a;
xdebug_debug_zval('a');

输出结果:a: (refcount=2, is_ref=0)=1 而当$b = &$a;refcount++,且is_ref = 1;is_ref=1表示该变量被引用了

当unset一个变量的时候refcount会-1,当refcount = 0的时候,对应的zval就会被释放掉.自己可以去试验下了解下.

refcount之数组类型

$a = array('a','b',array(1,2));
$b = &$a;
xdebug_debug_zval('a');

输出结果:

a:
(refcount=2, is_ref=1),
array (size=3)
0 => (refcount=1, is_ref=0),string 'a' (length=1)
1 => (refcount=1, is_ref=0),string 'b' (length=1)
2 => (refcount=1, is_ref=0),
array (size=2)
0 => (refcount=1, is_ref=0),int 1
1 => (refcount=1, is_ref=0),int 2

数组的形式比较复杂,可以看到输出结果,每个数组相当于一个zval,数组中得元素也相当于一个zval 每个zval里面都有refcount,以及is_ref 当$b = $a;的时候,$a对应的refcount会+1, 当$b = $a[1]的时候$a[1]的refcount会+1.

如果是使用类似$a[1] = &$a;这样得循环引用,是不是会出现什么问题?php5.3引入了新的gc垃圾回收机制来解决这个问题.

写时复制

$a = 'mingxin';
$b = $a;
$b = 'other str';
xdebug_debug_zval('a');
xdebug_debug_zval('b');

输出结果: a: (refcount=1, isref=0)='mingxin' b: (refcount=1, isref=0)='other str' 当你给$b赋予了新的值得时候,php发现$b指向的zval的refcount是>1的.这个时候就会初始化一个新的zval,并且给予这个新的zval一个初始值,(refcount = 1,is_ref = 0)这个赋予初值在zend.h中,对应的宏如下:

#define INIT_PZVAL(z) \
(z)->refcount__gc = 1; \
(z)->is_ref__gc = 0;

注:__gc其实就是引入了新的垃圾回收之后而变的.

#define SEPARATE_ZVAL(ppzv)
do {
if (Z_REFCOUNT_PP((ppzv)) > 1) {
zval *new_zv;
Z_DELREF_PP(ppzv);
ALLOC_ZVAL(new_zv);
INIT_PZVAL_COPY(new_zv, *(ppzv));
*(ppzv) = new_zv;
zval_copy_ctor(new_zv);
}
} while (0)

这个是分离操作,当变量进行写时复制,或者写时改变的时候都会进行这个操作. 大体意思应该看代码都了解了. 1.判断zval的refcount是否>1 2.如果大于1,那么new一个新的zval 3.原先的zval的refcount-1 4.ALLOW_ZVAL是给new zval申请分配内存. 5.将原先zval的值copy给new zval,并且初始化new zval,refcount = 1 6.修改符号表.使得zval和new zval分离.从而实现写时分离.

zval之type type用来记录zval的类型. phpsrc/zend/zend_types.h文件中记录了关于type的信息

/* data types /
/ All data types <= IS_BOOL have their constructor/destructors skipped */
#define IS_NULL 0
#define IS_LONG 1
#define IS_DOUBLE 2
#define IS_BOOL 3
#define IS_ARRAY 4
#define IS_OBJECT 5
#define IS_STRING 6
#define IS_RESOURCE 7
#define IS_CONSTANT 8
#define IS_CONSTANT_ARRAY 9
#define IS_CALLABLE 10

type中存储的是值类型,而真正得值是存放在zvalue中得. 如果zval.type=ISLONG ,那么zval.value就去取lval,如果zval.type=ISARRAY,那么zval.value就去取ht.

需要注意1: php中并不推荐大家使用&传引用方式. 例如下面这个实例:

echo memory_get_usage(),'->';
$aa = 'hello mogujie';
echo memory_get_usage(),'->';
$bb = &$aa;
echo memory_get_usage(),'->';
unset($bb);
echo memory_get_usage(),'->';

输出结果: 228808->228976->229064->228976

理论上$bb是$aa的值引用,当unset($bb)的时候$aa的值也应该被unset掉才是.但事实上php只会将$bb从hashtable中移除,且将refcount-1,而实际使用的内存没有任何变化.只有当指向该变量的所有变量都unset掉,这块的内存才会被释放.

需要注意2:

$ary = array_fill(1,100,'mogujie');
echo memory_get_usage(),'->';
foreach($ary as $k => $v){
$ary[$k] = NULL;
}
echo memory_get_usage(),'->';
foreach($ary as $k => $v){
unset($ary[$k]);
}
echo memory_get_usage();

输出结果: 226696->236840->241864->228264 为什么将数组声明在释放之后的内存占用是不一样得?

在声明变量的时候,变量都是存放在hashtable中得.而hashtable每次不知道你需要申请的内存有多大,因此他每次都只是申请一部分,当不够用的时候在扩容 但是当你unset掉变量的时候,因这些变量而扩容的hashtable并没有unset掉.所以有一部分的内存被hashtable占用了

That's All.

Woooowen 浙ICP备15013647号