php变量分离性能问题的浅析

php的copy on write,指的是变量发生改变时内存的分离机制,“写”不能简单的理解为赋值,而是必须满足变量的改变。
我们知道php的变量是由 zval实现的,下面是php5系列zval的定义:

typedef union _zvalue_value {
 long lval;                  
 double dval;                
 struct {
     char *val;
     int len;
 } str;
 HashTable *ht;              
 zend_object_value obj;
 zend_ast *ast;
} 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结构中,refcount是用来记录指向变量容器(zval)的符号个数,之所以有此设计,也是为了更加高效的利用内存。那写时分离到底是如何进行的呢?我们看下面的例子:

$val = 1;
debug_zval_dump($val);echo "

"; $val2 = $val; debug_zval_dump($val);echo "
"; debug_zval_dump($val2);echo "

"; $val3 = $val; debug_zval_dump($val);echo "
"; debug_zval_dump($val2);echo "
"; debug_zval_dump($val3);echo "

";

最终结果如下:

long(1) refcount(2) 

long(1) refcount(3) 
long(1) refcount(3) 

long(1) refcount(4) 
long(1) refcount(4) 
long(1) refcount(4) 

通过debug_zval_dump()函数吧变量在zend内部的表示输出出来,如果安装了xdebug,可以使用xdebug_debug_zval()函数达到类似的效果, 我们可以看到,$val的引用计数在不断增加,实际上$val2 = $val;$val3=$val...在执行的时候并没有发生内存复制,而是这几个变量的引用指向同一块内存地址。

接着看下面的例子:

$val = 1;
debug_zval_dump($val);echo "

"; $val2 = $val; debug_zval_dump($val);echo "
"; debug_zval_dump($val2);echo "

"; $val3 = $val; debug_zval_dump($val);echo "
"; debug_zval_dump($val2);echo "
"; debug_zval_dump($val3);echo "

"; $val3 = 2; debug_zval_dump($val);echo "
"; debug_zval_dump($val2);echo "
"; debug_zval_dump($val3);echo "
";

最终的结果为:

long(1) refcount(2) 

long(1) refcount(3) 
long(1) refcount(3) 

long(1) refcount(4) 
long(1) refcount(4) 
long(1) refcount(4) 

long(1) refcount(3) 
long(1) refcount(3) 
long(2) refcount(2) 

我们可以看到$val3被重新赋值之后,$val的引用计数减少了1,$val3被分离出来独占一块内存,$val和 $val2``` 继续指向统一内存地址。从这个过程中,我们大体看到分离的过程了。
当与引用结合之后,情况会有所不同

片段A:
$val = 1;
$val2 = $val;
echo "
";debug_zval_dump($val);echo "
"; $val3 = $val; //此处不会分离

所有的变量在未改变之前都是使用一个值,所以没有发生分离。后面谁改变就把谁分离出去,仍然不影响语义。

片段B:
$val = 1;
$val2 = &$val;
echo "
";debug_zval_dump($val);echo "
"; $val3 = $val; //此处会分离

语义上是 $val2$val 共享一个值,但val3单独一块内存,如果此处第四行仍然没有分离的话,语义会产生错误。此处第三行与上面第三行的区别在于$val的zval的is_ref变成了1,这时php并没有启动写时复制机制,而是直接进行复制了。也就是说向一个is_ref=0的变量要值,不会直接复制,在变量改变时才会复制。向一个is_ref=1的变量要值的时候,会直接触发复制。

片段C
function du($arg){}
$val = range(1,10);
$b = $val;
$start = microtime(true);
for($i=0;$i<=100000;$i++) {
   du($val); 
}
printf("time: %ss\n", microtime(true) - $start);

结果:

time: 0.042001962661743s

片段D
function du($arg){}
$val = range(1,10);
$b = &$val;
$start = microtime(true);
for($i=0;$i<=100000;$i++) {
   du($val); 
}
printf("time: %ss\n", microtime(true) - $start);

结果:
time: 0.17601108551025s

上面片段D代码之所以比片段C会消耗较更长的时间,就是因为执行d方法调用的时候,所经历的流程和片段B类似(d函数内部接收了参数的传值,并立刻复制了一份);
在php7发布之后,zval采用了新的机制,这个地方的性能问题已经不复存在。关于php7中是如何处理的,我们下回再讲。

参考资料:
http://php.net/manual/zh/language.references.pass.php
http://www.laruence.com/2018/04/08/3170.html

你可能感兴趣的:(php变量分离性能问题的浅析)