关于php引用的一般问题大家看资料就行了,这次我们来聊点有趣的东西。今天一个朋友在群里面问起来一个关于变量引用赋值的问题,问题本身很简单,我突然想做一个实验,来看看array直接赋值和引用赋值性能上的差别,写完代码发现另外一个问题.请看代码
<?php
$a = array_fill(0, 1000000, 10);
function test_time_cost($loop, $func) {
$start = microtime(true);
for($i = 0; $i < $loop; $i++) $func();
$elapsed = (microtime(true) - $start) / $loop;
echo "elapsed $elapsed\n";
};
function fetch_data($b) {}
test_time_cost(10000, function() {
global $a; fetch_data(&$a);
});
test_time_cost(10000, function() {
global $a; fetch_data($a);
});
代码在第二个测试函数里出错了,内存不够了。但是问题是php在变量赋值以后,如果目标变量未被使用,是不会执行赋值操作的。因为php语言在处理变量赋值时实际上是给$a和$b开辟了一个变量容器,在容器里$a和$b是对等的,可以简单理解为引用关系。但是当$b被改动时,$b就会被移出容器。
<?php
$a = array_fill(0, 1000000, 10);
$b = $a; // 内存不增加
$b[0] = 10; // $a所占内存会赋值给$b
fetch_data函数实际上未对变量做任何处理,怎么会触发复制呢?于是我写了下面的代码
<?php
echo "memory_limit = " . ini_get('memory_limit') ."\n";
echo "memory init = " . memory_get_usage(true) . "\n";
$a = array_fill(0, 10000, 10);
echo "memory after a = " . memory_get_usage(true) ."\n";
$c = $b = $a;
echo "memory after c,b = " . memory_get_usage(true) ."\n";
function fetch_data($m) {
echo "memory in fetch_data = " . memory_get_usage(true) ."\n";
}
fetch_data($a);
global $a; fetch_data($a);
运行结果如下
memory_limit = 128M
memory init = 786432
memory after a = 1835008
memory after c,b = 1835008
memory in fetch_data = 1835008
memory in fetch_data = 3670016
说明在声明global $a以后发生了变量拷贝赋值,既然是这样,看看php的文档吧,是不是对global申明的变量有什么特殊处理。看了一遍,没什么特殊的,就是创建引用对象。于是我又仔细看了一次php关于引用的说明,http://php.net/manual/en/features.gc.refcounting-basics.php. 我这次准备跟踪一下引用计数器
$a = array_fill(0, 1, 10);
$c = $b = $a;
echo "c,b,a\n";
xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');
echo "changing b\n";
$b[0] = 8;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');
echo "global a\n";
global $a;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');
echo "c,b,global a\n";
$c = $b = $a;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');
运行的结果
c,b,a
a: (refcount=3, is_ref=0)=array (0 => (refcount=1, is_ref=0)=10)
b: (refcount=3, is_ref=0)=array (0 => (refcount=1, is_ref=0)=10)
c: (refcount=3, is_ref=0)=array (0 => (refcount=1, is_ref=0)=10)
changing b
a: (refcount=2, is_ref=0)=array (0 => (refcount=1, is_ref=0)=10)
b: (refcount=1, is_ref=0)=array (0 => (refcount=1, is_ref=0)=8)
c: (refcount=2, is_ref=0)=array (0 => (refcount=1, is_ref=0)=10)
global a
a: (refcount=1, is_ref=1)=array (0 => (refcount=2, is_ref=0)=10)
b: (refcount=1, is_ref=0)=array (0 => (refcount=1, is_ref=0)=8)
c: (refcount=1, is_ref=0)=array (0 => (refcount=2, is_ref=0)=10)
c,b,global a
a: (refcount=1, is_ref=1)=array (0 => (refcount=2, is_ref=0)=10)
b: (refcount=2, is_ref=0)=array (0 => (refcount=2, is_ref=0)=10)
c: (refcount=2, is_ref=0)=array (0 => (refcount=2, is_ref=0)=10)
终于看出了问题,在没有global $a的时候,a,b,c在同一个变量容器里,所以没有发生复制行为。但是在声明global $a以后再进行复制,b,c不和a在同一容器了。我觉得原因就在于$a这个时候已经不是简单的变量了,可以理解为一个指针。php在这个时候没有办法创建变量容器同时给变量和指针,所以拷贝于是就发生了。
希望今天这个实验能加深大家对php引用的理解。我的微博 "James胡建",QQ号码 914244905,欢迎关注我。