手机版

PHP之写时复制介绍(写时复制)

时间:2021-11-08 来源:互联网 编辑:宝哥软件园 浏览:

在开始之前,我们可以看一个简单的代码:复制代码如下:Php //示例1 $ foo=1$ bar=$ fooecho $ foo $ bar?执行该代码时,将打印数字2。从内存的角度来看,这段代码“可能”执行如下:分配一块内存给foo变量,并在其中存储一个1;然后给bar变量分配一块内存,也存储一个1,最后计算结果输出。事实上,我们发现foo和bar变量可以使用相同的内存块,因为它们具有相同的值。结果,存储器的使用节省了1,并且分配存储器和管理存储器地址的计算开销也被节省。是的,很多涉及内存管理的系统都实现了这种同值共享内存的策略:写的时候复制,我们会因为一些术语对它的概念产生深不可测的恐惧,但其实它们的基本原理往往很简单。本节将介绍写时复制策略在PHP中的实现:写时复制(COW)有很多应用场景,比如Linux中进程复制中内存使用的优化,以及各种编程语言中的类似应用,比如C的STL等。COW是一种常见的优化方法,可以分为:延迟资源分配。资源只有在真正需要的时候才会被占用,写时复制通常可以减少资源的占用。注意:为了节省空间,COW将用于表示“写入时复制”。延迟内存复制的优化如前所述,PHP中的COW可以简单描述为:通过赋值给变量赋值,不会申请新的内存来存储新变量中存储的值,而只是使用一个计数器来共享内存,只有当其中一个引用指向变量的值发生变化时,才申请新的空间来存储值内容,从而减少内存占用。在许多场景中,PHP COW优化了它的内存。例如变量的多次赋值、函数参数的传递、函数体中实际参数的修改等。我们来看一个检查内存的例子,很容易看出COW在优化内存使用方面的明显作用:复制代码如下:Php //示例2 $ j=1;var _ dump(memory _ get _ usage());$tipi=array_fill(0,100000,' PHP-internal ');var _ dump(memory _ get _ usage());$ tipi _ copy=$ tipivar _ dump(memory _ get _ usage());foreach($ tipi _ copy as $ I){ $ j=count($ I);} var _ dump(memory _ get _ usage());//-执行结果-$ PHP t.php int(630904)int(10479840)int(10479944)int(10480040)上面的代码通常突出了COW的作用,当数组变量$tipi被分配给$tipi_copy时。当循环遍历次数为$tipi_copy时,没有显著变化。这里,$tipi_copy和$tipi变量的数据指向同一个内存,但没有被复制。也就是说,即使我们不使用引用,在赋值一个变量之后,只要我们不改变变量的值,我们就不会申请新的内存来存储数据。基于此,我们很容易想到一些COW可以有效控制内存使用的场景:它只使用变量进行计算,很少修改,比如函数参数的传递和大数组的复制等。而不改变变量值。复制更改的值多个值相同的变量共享相同的内存,这确实节省了内存空间,但是变量的值会发生变化。如果在上面的例子中指向相同存储器的值已经改变(或者可能改变),则有必要“分离”改变的值。这种“分离”操作称为“复制”。在PHP中,Zend引擎引入了ref_count和is_ref两个变量来标识同一个zval地址是否被多个变量共享:复制代码的代码如下:在zval结构中定义了:ref_count和is_ref(见第一章第一节),is_ref标识是否是用户使用的强制引用;ref_count是引用计数,用来标识这个zval被引用了多少个变量,也就是COW的自动引用,为0时会被销毁;有关这两个变量的更多信息,请跳过阅读:第3章,第6节:变量赋值和销毁的实现。

注:因此,$ a=$ b;而$ a=$ b;PHP中内存的使用没有区别(值不变时);让我们稍微改变一下示例2:如果$copy的值发生变化,会发生什么情况?复制代码如下:Php //示例3//$ tipi=array _ fill (0,3,' PHP-内部');//array_fill不再用于此处填充。为什么呢?$ tipi[0]=“PHP-内部”;$ tipi[1]=“PHP-内部”;$ tipi[2]=' PHP-内部';var _ dump(memory _ get _ usage());$ copy=$ tipixdebug_debug_zval('tipi ',' copy ');var _ dump(memory _ get _ usage());$ copy[0]=“PHP-内部”;xdebug_debug_zval('tipi ',' copy ');var _ dump(memory _ get _ usage());//-执行结果-$ PHP t.php int(629384)tipi :(ref count=2,is _ ref=0)=array (0=(refcount=1,is _ ref=0)=' PHP-internal ',1=。is_ref=0)='php-internal ',2=(refcount=1,is_ref=0)='php-internal ' copy :(ref count=2,is_ref=0)=array (0=(refcount=1,is_ref=0)='php-internal ',1=(refcount=1,Is _ ref=0)=' PHP-internal ',2=(refcount=1,Is _ ref=0)=' PHP-internal ')int(629512)tipi 333332这个基本的赋值操作会触发COW的内存“共享”,不会产生内存复制。COW的粒度是zval结构,PHP中的所有变量都是基于zval的,所以COW的范围是所有变量。对于由zval结构组成的集合(如数组和对象等。),当需要复制内存时,将复杂对象分解到最小粒度进行处理。这样,当在内存中修改对象的某个部分时,就不需要将复杂对象的所有元素“分离并复制”到内存副本中。复制代码如下:array_fill()在填充数组时也使用了COW策略,可能会影响这个例子的演示。感兴趣的读者可以阅读$ PHP _ src/ext/standard/array.c中PHP_FUNCTION(array_fill)的实现,Xbug _ xdebug_debug_zval()是xdbug扩展中的一个函数,用来输出zend中变量的引用信息。如果没有安装xdebug扩展,也可以使用debug_zval_dump()来代替。参考:http://www.php.net/manual/zh/function.debug-zval-dump.php实现写时复制看完上面三个例子,相信大家也能明白COW在PHP中的实现原理:PHP中的COW是基于引用计数ref_count和is_ref实现的,如果多了一个变量指针,ref_count就会增加1,否则减为0就会被破坏;同样,如果多了一个强制引用,is_ref会增加1,否则会减少1。下面是一个典型的例子:复制代码如下:Php //示例4 $ foo=1;xdebug _ debug _ zval(' foo ');$ bar=$ fooxdebug _ debug _ zval(' foo ');$ bar=2;xdebug _ debug _ zval(' foo ');//-执行结果-foo: (refcount=1,is _ ref=0)=1foo3360 (refcount=2,is _ ref=0)=1foo3360 (refcount=1,is _ ref=0)。当$foo的值被分配给$bar时,PHP不会给$bar一个内存副本,而是将$foo和$bar指向同一个地址。同时,参考计数增加1,这是新的2。然后,我们改变了$bar的值。此时,如果直接需要$bar变量指向的内存,那么$foo的值也会发生变化。这不是我们想要的结果。因此,PHP内核复制了一个内存副本,并将其值更新为赋值:2(该操作也称为变量分离操作)。同时,原始$foo变量指向的内存只有$foo,因此引用计数更新为:refcount=1。看起来很简单,但实际情况要复杂得多,因为有操作符的存在。请看下面的例子:

图6.6操作符导致的内存复制分离从这个例子中,我们可以看到PHP很容易处理操作符:当$ beauty=$ pan时,两个变量本质上都变成了引用类型,导致看似普通的变量$pan,在一些内部处理中表现得和$pan一样,特别是在数组元素中使用引用变量时,很容易出现问题。(见最后一个例子)PHP的大部分工作是文本处理,变量是载体。在PHP的整个生命周期中使用不同类型的变量。变量的COW策略也反映了Zend引擎对变量及其内存的处理。具体请参考源代码文件的相关内容:复制代码如下: Zend/Zend _ execute . c======================Zend _ assign _。Zend _ assign _ to _ variable();Zend _ assign _ to _ object();Zend _ assign _ to _ variable();//以及Zend/Zend . h===========================================================#定义z _ refcount (z)。rc) Z_SET_REFCOUNT_P((z),RC)#定义Z _ ADDREF(Z)Z _ ADDREF _ P((Z))#定义Z _ DELREF(Z)Z _ DELREF _ P((Z))#定义Z _ ISREF(Z)Z _ ISREF _ P((Z))#定义Z _ ISREF _ P((Z))#定义Z_SET _ ISREF(Z)Z _ SET _ ISREF _ P((Z))#定义Z _ unset _ ISREF(Z)Z _ unset _ ISREF _ P((Z))#定义Z _ SET请谨慎使用参考。上面提到的引用和变量的引用计数与PHP中的引用不是一回事。引用类似于C语言中的指针,它们可以通过不同的标签访问相同的内容。然而,PHP中的引用只是简单的变量别名,没有C指令的灵活性和局限性。PHP中有很多意想不到的行为,有些因为历史原因无法破坏兼容性,所以选择暂时不修复,或者有些使用场景很少。在PHP中,我们只能尽可能避免这些陷阱。例如,下面的例子。由于引用操作符会导致PHP的COW策略的优化,所以需要对引用的行为有一个清晰的了解,避免误用,带来一些难以理解的bug。如果你认为自己对PHP中的引用已经足够了解,可以试着解释一下下面的例子:复制代码如下:PHP $ foo[' love ']=1;$ bar=$ foo[' love '];$ tipi=$ foo$ tipi[' love ']=' 2 ';echo $ foo[' love '];最后这个例子会输出2,大家会很惊讶$tipi如何影响$foo和$ foo、$bar变量的引用操作,把$foo['love']污染变成引用,从而Zend没有修改$tipi['love'],导致内存重复和分离。

版权声明:PHP之写时复制介绍(写时复制)是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。