php变量之写时复制机制(copy on write)
编程思想虽然可以共用,不过语言间的差异还是比较明显的,只是使用者之间没有意识到而己,而了解其中的差异对于编写程序以及把握性能还是有好处的。下面我们来介绍下PHP的一个很重要的机制copy on write,我们先以最简单的变量来介绍这个机制,在说这个之前,笔者先来介绍下弱类型是怎么实现的。
大家都知道,PHP是由C实现的,可是C是强类型语言,PHP怎么做到弱类型语言。一起来看下,PHP变量在C语言底层中的代码,
typedef struct _zval_struct zval; typedef unsigned int zend_uint; typedef unsigned char zend_uchar; struct _zval_struct { zvalue_value value; /*注意这里,这个里面存的才是变量的值*/ zend_uint refcount__gc; /*引用计数*/ zend_uchar type; /* 变量当前的数据类型 */ zend_uchar is_ref__gc; /*变量是否引用*/ }; typedef union _zvalue_value { long lval; /*PHP中整型的值*/ double dval; /*PHP的浮点数值*/ struct { char *val; int len; } str; /*PHP的字符串*/ HashTable *ht; /*数组*/ zend_object_value obj; /*对象*/ } zvalue_value;
本人加了点注释,大家可以发现,实际上我们在PHP用的变量,低层是一个结构体zval,里面的zvalue_value结构体实际上是个联合体,这个联合体才是实际存放着PHP的变量值,下面我们以实际的PHP代码例子来表示整个工作过程,注意上面的引用计数。先来看C语言的,首先是非函数部分,函数部分下一章节来讲
int i = 4; //alloca方式在内存中分配空间,这个变量在内存中的栈区 int j = i; //alloca方式在内存中分配空间,并且将原先内存空间里面的数据复制到新的内存空间中,这个变量在内存的栈区 int j = 5; //不分配内存空间,对变量j所在的栈区空间的数据进行修改
来看PHP部分的
$i = 4; //内核创建一个zval指针,并且为其以堆的方式开辟空间,让指针指向这个空间,将zval中的成员引用计数置为1,类型标记为整形,并且申请一个zvalue_value指针,同样以堆的方式以其开辟空间,同时将该联合体中的lval赋值为4,并且在symbal_table的hash表中记录变量i和zval指针的映射关系 $j = $i; //没有在申请内存空间,在zval的成员中引用计数标记为2 $j = 5; //内核重新创建zval指针,重复下上面的步骤,我就不重复说明了,重点是将旧的zval引用计数标记为1
从这个地方发现几个重要点
1.所有的php变量开辟的内存空间都是在堆中,无论是临时变量还是全局变量,只是php的临时变量记录在active_symbal_table表中,全局变量记录中symbal_table表中
2.php干嘛比C慢。多做了这么多事,能不慢吗?
3.当php类似$j = $i这种变量赋值时,是没有内存开销的,也就是你赋值个几万个,也只是引用计数变成几万而己,这个和C语言是不一样的。而当变量的值发生变化时,才会进行重新开辟内存空间,这个机制我们称为写时复制机制
额外细节部分,当php内核发现,int的数值溢出时,也就是超出整型的范围时,自动转换为float,有兴趣的读者可以自己写个很大的整型,但是不能超出float取值范围,看看var_dump数据类型是什么。
最后部分:
php对象部分因为默认是引用方式的,所以就是赋值完,再改变对象的成员变量,也不会启用写时复制的,如以下
class Test { public $var = 999; } $test1 = new Test(); $test2 = $test1; //只是引用计数加1而己,没有开辟新的内存空间 $test2->var = 1000; echo $test1->var; //此时的值也为1000 $test3 = clone $test1; //这个才是正在重新开辟新的内存空间