标签:blog http io 使用 ar strong for 数据 2014
原文:http://www.huamanshu.com/blog/2014-05-18.html
起源
写时复制英文名字叫“copy-on-write”,简称“cow”,又名“靠”
今天查了下这个"cow",原来起源于*nix内存管理机制,后来广泛应用在其它开发语言上,C++的STL,还有PHP,相信很多人都了解这个写时复制,经常跟别人侃得甚欢。
$foo = ‘foo‘;
$bar = $foo;
不会
初写这样的PHP代码时,常会问,这样的话会不会因为复制而占用更多内存,旁边会有人说,不会。当然你不知道他说的“不会”,是指“不占用”,还是“我不知道”。后来,了解到memory_get_usage()
这函数后,亲自测试验证,确实没有成倍增加。
<?php
var_dump(memory_get_usage());
$foo = ‘foooooooooooooooooooooooooooooo‘;
var_dump(memory_get_usage());
$bar = $foo;
var_dump(memory_get_usage());
------ 结果 -------
int(121704)
int(121840) // 与前者相差 echo $((840-704)) = 136
int(121888) // 与前者相差 echo $((888-840)) = 48
xdebug管中窥豹
神奇的是无论$foo
变量多大,复制$foo
到$bar
时,内存消耗只增加48,这48是什么东西呢?这个时候,得请xdebug来了解下,到底发生了什么事情。
<?php
$foo = ‘foo‘;
xdebug_debug_zval(‘foo‘);
$bar = $foo;
xdebug_debug_zval(‘foo‘);
xdebug_debug_zval(‘bar‘);
$bar = ‘bar‘;
xdebug_debug_zval(‘foo‘);
xdebug_debug_zval(‘bar‘);
------ 结果 -------
foo: (refcount=1, is_ref=0)=‘foo‘ // copy foo before
foo: (refcount=2, is_ref=0)=‘foo‘ // copy foo after
bar: (refcount=2, is_ref=0)=‘foo‘ // write bar befor
foo: (refcount=1, is_ref=0)=‘foo‘ // write bar after
bar: (refcount=1, is_ref=0)=‘bar‘ // write bar after
注意观察会发现refcount的值在copy foo和write bar的时候发生了变化,这过程就是PHP的写时复制。好吧,我这样说,跟高中老师说的,看这就是能量守恒定律一样无趣。
PHP变量结构体
其实refcount在PHP内核中,是变量存储的结构体的一个成员,完整名称refcount__gc
,上述xdebug打印出来的is_ref也是变量结构体的一个成员is_ref__gc
,查看它完整的定义可以查看PHP源码Zend/zend.h
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
refcount__gc
是记录变量引用次数,当$bar = $oo
的时候,foo的refcount加一,由于bar与foo是同指向一个变量,引用计数同为2。当bar值被改写时,发生了写时复制,这结构体指向的内在块已经不能同时支持两个值了,于是内核就做了复制一份foo出来给bar,并设置其值为bar。is_ref__gc
是用来标识是否为引用的,也就是说当我们对变量进行引用的时候,它就派上用场了。
我们先来看PHP内核是如何创建为我们创建一个变量,复制变量和引用变量。PHP是一个弱类型变量语言,没有type的说法,但PHP是由C写的,C类型定义和使用有着严格的要求。
创建变量
<?php
$foo = ‘FOO‘
// PHP内核创建$foo变量
zval *foovar;
MAKE_STD_ZVAL(foovar); // 内核专门封装为变量申请内存空间的宏
ZVAL_STRING(fooval, "FOO", 1); // type => ZVAL_STRING, value => "FOO"
ZEND_SET_SYMBOL(EG(active_symbol_table), "foo", foovar); // 把$foo加入当前作用域的符号表中,使之在后面可以在symbo_table中可以检索出$foo,也就是PHP代码中要使用$foo的时候。
现在看来我们简单的写了一个$foo = ‘FOO‘;
,内核是这样来给我们做底层的事情,我们不用担心内存到底应该分配多少,还得释放,PHP是不是很人性,让PHP开发者轻松就入门来,看到PHP的世界。
复制变量
<?php
$foo = ‘FOO‘;
$bar = $foo;
// PHP内核创建$foo变量
zval *foovar;
MAKE_STD_ZVAL(foovar);
ZVAL_STRING(fooval, "FOO", 1); // type => ZVAL_STRING, value => "FOO"
ZEND_SET_SYMBOL(EG(active_symbol_table), "foo", foovar); //
ZVAL_ADDREF(foovar); //显式的增加了foovar结构体的refcount__gc
zend_hash_add(EG(active_symbol_table), "bar", sizeof("bar"),&foovar, sizeof(zval*), NULL);
引用变量
<?php
$foo = ‘FOO‘;
$bar = &$foo;
// PHP内核创建$foo变量
zval *foovar;
MAKE_STD_ZVAL(foovar);
ZVAL_STRING(fooval, "FOO", 1); // type => ZVAL_STRING, value => "FOO"
zend_hash_add(EG(active_symbol_table), "bar", sizeof("bar"),&foovar, sizeof(zval*), NULL);
zend_hash_add(EG(active_symbol_table), "bar", sizeof("bar"),&foovar, sizeof(zval*), NULL); // $bar、$foo同指向一个内在地址
复制、引用多重组合
<?php
$foo = ‘foo‘;
xdebug_debug_zval(‘foo‘);
$bar = $foo;
xdebug_debug_zval(‘foo‘);
xdebug_debug_zval(‘bar‘);
// 增加一个引用
$hua = &$foo;
xdebug_debug_zval(‘foo‘);
xdebug_debug_zval(‘bar‘);
xdebug_debug_zval(‘hua‘);
------ 结果 -------
1.foo: (refcount=1, is_ref=0)=‘foo‘ // copy foo befor
2.foo: (refcount=2, is_ref=0)=‘foo‘ // copy foo after
3.bar: (refcount=2, is_ref=0)=‘foo‘
4.foo: (refcount=2, is_ref=1)=‘foo‘ // ref foo after
5.bar: (refcount=1, is_ref=0)=‘foo‘
6.hua: (refcount=2, is_ref=1)=‘foo‘
都知道当引用的时候,改变foo,?hua两者其一值,另外一变量也跟着变化。总结起来就是
写时复制、引用多重组合变态版
<?php
$foo[‘hua‘] = ‘man‘;
$bar = &$foo[‘hua‘];
$huamanshu = $foo;
$huamanshu[‘hua‘] = ‘shu‘;
echo $foo[‘hua‘];
猜下结果是什么?都说了是变态版,怎么能按常识去想答案呢?正确答案是:‘shu‘。这个$huamanshu
跟我们的$foo
扯不上半毛钱关系啊?
<?php
$foo [‘hua‘] = ‘man‘;
xdebug_debug_zval(‘foo‘);
$bar = &$foo[‘hua‘];
xdebug_debug_zval(‘foo‘);
xdebug_debug_zval(‘bar‘);
$huamanshu = $foo;
xdebug_debug_zval(‘foo‘);
xdebug_debug_zval(‘huamanshu‘);
$huamanshu[‘hua‘] = ‘shu‘;
xdebug_debug_zval(‘foo‘);
xdebug_debug_zval(‘huamanshu‘);
xdebug_debug_zval(‘bar‘);
$ php y.php
foo: (refcount=1, is_ref=0)=array (‘hua‘ => (refcount=1, is_ref=0)=‘man‘)
foo: (refcount=1, is_ref=0)=array (‘hua‘ => (refcount=2, is_ref=1)=‘man‘)
bar: (refcount=2, is_ref=1)=‘man‘
foo: (refcount=2, is_ref=0)=array (‘hua‘ => (refcount=2, is_ref=1)=‘man‘)
huamanshu: (refcount=2, is_ref=0)=array (‘hua‘ => (refcount=2, is_ref=1)=‘man‘)
foo: (refcount=1, is_ref=0)=array (‘hua‘ => (refcount=3, is_ref=1)=‘shu‘)
huamanshu: (refcount=1, is_ref=0)=array (‘hua‘ => (refcount=3, is_ref=1)=‘shu‘)
bar: (refcount=3, is_ref=1)=‘shu‘
个人觉得$foo[‘hua‘]
的refcount
的值在$huamanshu[‘hua‘] = ‘shu‘;
的时候,应该由2变成0,而不是继续加一。
当然,会有不少的同学会犯这样的毛病:
$array = [ 1, 2, 3, 4 ];
foreach ($array as &$value) {}
echo implode(‘,‘, $array), PHP_EOL;
// unset($value); 解决办法之一
foreach ($array as $value) {}
echo implode(‘,‘, $array), PHP_EOL;
// echo
1,2,3,4
1,2,3,3
是因为第一个foreach
之后,$value
的引用保持,后面涉及$value
的时候,都会改变$value
值,也就是$array
的最后一个元素值。第二个foreach
的每一次都在改变$value
值,只是到了第三次就改写了$value
值,第四次只是重复赋刚才第三次覆盖的值而已。所以,在优化PHP代码的时候,foreach最好写不一样的
$v`,或者`$item`之类的名字,避免上面用引用而导致改写,实在不行就用unset掉用引计数。
其实,PHP很聪明的,当然每个语言都是,尤其是C,当之无愧!PHP内核还专门定义了8种类型来支持我们在PHP代码中创建的所有类型,太让我们变懒惰了。看完这个类型列表,会对PHP内核实现有更深的了解。
8种类型引自www.walu.cc
原文:http://www.huamanshu.com/blog/2014-05-18.html
标签:blog http io 使用 ar strong for 数据 2014
原文地址:http://www.cnblogs.com/wushuiyong/p/3971056.html