zvalue_value 联合体:
typedef union _zvalue_value {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
} zvalue_value;
zval 结构:
struct _zval_struct {
zvalue_value value; /* 存储了变量的实际数据 */
zend_uint refcount; /* 引用计数,记录引用数 */
zend_uchar type; /* 表示这个变量的类型 */
zend_uchar is_ref; /* 标志这个容器是否真正的引用 */
};
zval可以看成一个容器,zvalue_value是该容器存储变量值的联合体,zend根据type值来决定访问value的哪个成员,可用值如下:
IS_NULL | N/A |
IS_LONG | 对应value.lval |
IS_DOUBLE | 对应value.dval |
IS_STRING | 对应value.str |
IS_ARRAY | 对应value.ht |
IS_OBJECT | 对应value.obj |
IS_BOOL | 对应value.lval. |
IS_RESOURCE | 对应value.lval |
根据这个表格可以发现:
1) PHP的数组其实就是一个HashTable,这就解释了为什么PHP能够支持关联数组了;
2) Resource就是一个long值,它里面存放的通常是个指针、一个内部数组的index或者其它什么只有创建者自己才知道的东西,可以将其视作一个handle。
写复制(copy on write)
<?php
$var = "laruence";
$var_dup = $var;
$var = 1;
?>
PHP在修改一个变量以前,会首先查看这个变量的refcount,如果
refcount大于1,PHP就会执行一个分离的例程, 对于上面的代码,当执行到第三行的时候,PHP发现$var指向的zval的
refcount大于1,那么PHP就会复制一个新的zval出来,将原zval的
refcount减1,并修改symbol_table,使得$var和$var_dup分离(Separation)。这个机制就是所谓的copy on write(写时复制)。
写改变(change on write)
<?php
$var = "laruence";
$var_ref = &$var;
$var_ref = 1;
?>
开始在zval里面我们看到一个字段is_ref,到底如何是怎样产生作用的呢?
现在我们知道,当使用变量复制的时候 ,PHP内部并不是真正的复制,而是采用指向相同的结构来尽量节约开销。那么,对于PHP中的引用,那又是如何实现呢?
以上代码结束后,$var也会被间接的修改为1,这个过程称作(change on write:写时改变)。那么zend是怎么知道,这次的复制是不需要Separation的呢?
这个时候就要用到zval中的 is_ref字段了:
对于上面的代码,当第二行执行以后,$var所代表的zval的refcount变为2,并且同时置is_ref为1。
当使用引用时,PHP会把is_ref即程序会如下判断该引用是否真实引用,PHP先检查var_ref代表的zval的is_ref字段,如果为1,则不分离,大体逻辑示意如下:
<?php
if((*val)->is_ref || (*val)->refcount<2){
//不执行Separation
... ; //process
}
?>
两种方式到底怎样使用,啥时候使用?
<?php
$var = "laruence";
$var_dup = $var;
$var_ref = &$var;
?>
对于上面的代码,存在一对copy on write的变量$var和$var_dup,和一对change on write机制的变量对$var和$var_ref,这个情况又是如何运作的呢?
当第二行执行的时候,和前面讲过的一样,$var_dup 和 $var 指向相同的zval, refcount为2。
当执行第三行的时候,PHP发现要操作的zval的refcount大于1,则,PHP会执行Separation,将$var_dup分离出去,并将$var和$var_ref做change on write关联。即refcount=2,is_ref=1。
数值巨大变量处理
如果我们在php中进行参数传递的时候,是否有必要对传递内容巨大的数组心存警惕,担心内存的大量流失?<?php
function countChina($persons) {
$count=0;
foreach($persons as $person) {
if($person['nation'] == 'china') $count++;
}
return $count;
}
?>
例如这段统计一群人中有多少个中国国籍的时候,如果有一个10W的人的数组要传进去,你是否会担心被复制后传到函数里,导致内存占用瞬间翻倍?也许你会在参数那里加一个&表示引用?
php的设计者在这里有一个很巧妙的设计,引入了一个copy on write的概念,如果不去修改$perons对象的内容,并不会有复制行为发生,内存也不会double。 但如果想在函数里面修改数据,便于函数后面的处理,这时内存会double吗?如下:
<?php
function countChina($persons){
$count=0;
foreach($persons as $person){
if($person['nation'] == 'china'){
$person['score'] = 10;
$count++;
}
}
//利用score做一些处理...
return $count;
}
如果你不在参数里加&的话,内存消耗会逐步增加,在返回以前,内存的占用会double。因为为了满足write需要,PHP进行了copy,这个copy不是一次完成的,在改变第一个$persons成员的时候,会将$persons数组和其成员的地址复制过来,并不会把所有成员内容都复制过来,随着$persons成员的一个个被write,一个个也都被copy过来,内存占用线性增加,到循环结束时,double了!如果确实要修改内容,又想避免内存消耗,又不怕影响参数变量在其他地方的使用,那就在参数里加&吧。
要释放被占用的内存变量,用unset($persons)或者$persons=null都可以迅速地释放掉,你可以使用memory_get_usage进行监控。