真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

PHP中寫(xiě)時(shí)復(fù)制的示例分析-創(chuàng)新互聯(lián)

小編給大家分享一下PHP中寫(xiě)時(shí)復(fù)制的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

金山網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)公司!從網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、APP開(kāi)發(fā)、響應(yīng)式網(wǎng)站開(kāi)發(fā)等網(wǎng)站項(xiàng)目制作,到程序開(kāi)發(fā),運(yùn)營(yíng)維護(hù)。創(chuàng)新互聯(lián)公司2013年開(kāi)創(chuàng)至今到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來(lái)保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)公司。

在開(kāi)始之前,我們可以先看一段簡(jiǎn)單的代碼:

    $foo = 1;
    $bar = $foo;
    echo $foo + $bar;
?>


 執(zhí)行這段代碼,會(huì)打印出數(shù)字2。從內(nèi)存的角度來(lái)分析一下這段代碼“可能”是這樣執(zhí)行的:分配一塊內(nèi)存給foo變量,里面存儲(chǔ)一個(gè)1; 再分配一塊內(nèi)存給bar變量,也存一個(gè)1,最后計(jì)算出結(jié)果輸出。事實(shí)上,我們發(fā)現(xiàn)foo和bar變量因?yàn)橹迪嗤?,完全可以使用同一塊內(nèi)存,這樣,內(nèi)存的使用就節(jié)省了一個(gè)1,并且,還省去了分配內(nèi)存和管理內(nèi)存地址的計(jì)算開(kāi)銷。沒(méi)錯(cuò),很多涉及到內(nèi)存管理的系統(tǒng),都實(shí)現(xiàn)了這種相同值共享內(nèi)存的策略:寫(xiě)時(shí)復(fù)制

很多時(shí)候,我們會(huì)因?yàn)橐恍┬g(shù)語(yǔ)而對(duì)其概念產(chǎn)生莫測(cè)高深的恐懼,而其實(shí),他們的基本原理往往非常簡(jiǎn)單。本小節(jié)將介紹PHP中寫(xiě)時(shí)復(fù)制這種策略的實(shí)現(xiàn):

寫(xiě)時(shí)復(fù)制(Copy on Write,也縮寫(xiě)為COW)的應(yīng)用場(chǎng)景非常多, 比如Linux中對(duì)進(jìn)程復(fù)制中內(nèi)存使用的優(yōu)化,在各種編程語(yǔ)言中,如C++的STL等等中均有類似的應(yīng)用。 COW是常用的優(yōu)化手段,可以歸類于:資源延遲分配。只有在真正需要使用資源時(shí)才占用資源, 寫(xiě)時(shí)復(fù)制通常能減少資源的占用。

注: 為節(jié)省篇幅,下文將統(tǒng)一使用COW來(lái)表示“寫(xiě)時(shí)復(fù)制”;

推遲內(nèi)存復(fù)制的優(yōu)化

       正如前面所說(shuō),PHP中的COW可以簡(jiǎn)單描述為:如果通過(guò)賦值的方式賦值給變量時(shí)不會(huì)申請(qǐng)新內(nèi)存來(lái)存放新變量所保存的值,而是簡(jiǎn)單的通過(guò)一個(gè)計(jì)數(shù)器來(lái)共用內(nèi)存,只有在其中的一個(gè)引用指向變量的值發(fā)生變化時(shí)才申請(qǐng)新空間來(lái)保存值內(nèi)容以減少對(duì)內(nèi)存的占用。在很多場(chǎng)景下PHP都COW進(jìn)行內(nèi)存的優(yōu)化。比如:變量的多次賦值、函數(shù)參數(shù)傳遞,并在函數(shù)體內(nèi)修改實(shí)參等。

下面讓我們看一個(gè)查看內(nèi)存的例子,可以更容易看到COW在內(nèi)存使用優(yōu)化方面的明顯作用:

$j = 1;
        var_dump(memory_get_usage());

$tipi = array_fill(0, 100000, 'php-internal');
        var_dump(memory_get_usage());

$tipi_copy = $tipi;
        var_dump(memory_get_usage());

foreach($tipi_copy as $i){
    $j += count($i); 
}
        var_dump(memory_get_usage());

//-----執(zhí)行結(jié)果-----
$ php t.php 
int(630904)
int(10479840)
int(10479944)
int(10480040)


上面的代碼比較典型的突出了COW的作用,在數(shù)組變量$tipi被賦值給$tipi_copy時(shí),內(nèi)存的使用并沒(méi)有立刻增加一半,在循環(huán)遍歷數(shù)$tipi_copy時(shí)也沒(méi)有發(fā)生顯著變化,在這里$tipi_copy和$tipi變量的數(shù)據(jù)共同指向同一塊內(nèi)存,而沒(méi)有復(fù)制。

       也就是說(shuō),即使我們不使用引用,一個(gè)變量被賦值后,只要我們不改變變量的值 ,也不會(huì)新申請(qǐng)內(nèi)存用來(lái)存放數(shù)據(jù)。據(jù)此我們很容易就可以想到一些COW可以非常有效的控制內(nèi)存使用的場(chǎng)景:只是使用變量進(jìn)行計(jì)算而很少對(duì)其進(jìn)行修改操作,如函數(shù)參數(shù)的傳遞,大數(shù)組的復(fù)制等等等不需要改變變量值的情形。

復(fù)制分離變化的值

        多個(gè)相同值的變量共用同一塊內(nèi)存的確節(jié)省了內(nèi)存空間,但變量的值是會(huì)發(fā)生變化的,如果在上面的例子中,指向同一內(nèi)存的值發(fā)生了變化(或者可能發(fā)生變化),就需要將變化的值“分離”出去,這個(gè)“分離”的操作,就是“復(fù)制”。

       在PHP中,Zend引擎為了區(qū)別同一個(gè)zval地址是否被多個(gè)變量共享,引入了ref_count和is_ref兩個(gè)變量進(jìn)行標(biāo)識(shí):

復(fù)制代碼 代碼如下:


ref_count和is_ref是定義于zval結(jié)構(gòu)體中(見(jiàn)第一章第一小節(jié))
is_ref標(biāo)識(shí)是不是用戶使用 & 的強(qiáng)制引用;
ref_count是引用計(jì)數(shù),用于標(biāo)識(shí)此zval被多少個(gè)變量引用,即COW的自動(dòng)引用,為0時(shí)會(huì)被銷毀;
關(guān)于這兩個(gè)變量的更多內(nèi)容,跳轉(zhuǎn)閱讀:第三章第六節(jié):變量的賦值和銷毀的實(shí)現(xiàn)。
注:由此可見(jiàn), $a=$b; 與 $a=&$b; 在PHP對(duì)內(nèi)存的使用上沒(méi)有區(qū)別(值不變化時(shí));


下面我們把例二稍做變化:如果$copy的值發(fā)生了變化,會(huì)發(fā)生什么?:

復(fù)制代碼 代碼如下:


//$tipi = array_fill(0, 3, 'php-internal');  
//這里不再使用array_fill來(lái)填充 ,為什么?
$tipi[0] = 'php-internal';
$tipi[1] = 'php-internal';
$tipi[2] = 'php-internal';
var_dump(memory_get_usage());

$copy = $tipi;
xdebug_debug_zval('tipi', 'copy');
var_dump(memory_get_usage());

$copy[0] = 'php-internal';
xdebug_debug_zval('tipi', 'copy');
var_dump(memory_get_usage());

//-----執(zhí)行結(jié)果-----
$ php t.php 
int(629384)
tipi: (refcount=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')
copy: (refcount=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: (refcount=1, is_ref=0)=array (0 => (refcount=1, is_ref=0)='php-internal', 
                                    1 => (refcount=2, is_ref=0)='php-internal', 
                                    2 => (refcount=2, is_ref=0)='php-internal')
copy: (refcount=1, is_ref=0)=array (0 => (refcount=1, is_ref=0)='php-internal', 
                                    1 => (refcount=2, is_ref=0)='php-internal', 
                                    2 => (refcount=2, is_ref=0)='php-internal')
int(630088)


在這個(gè)例子中,我們可以發(fā)現(xiàn)以下特點(diǎn):

$copy = $tipi;這種基本的賦值操作會(huì)觸發(fā)COW的內(nèi)存“共享”,不會(huì)產(chǎn)生內(nèi)存復(fù)制;

COW的粒度為zval結(jié)構(gòu),由PHP中變量全部基于zval,所以COW的作用范圍是全部的變量,而對(duì)于zval結(jié)構(gòu)體組成的集合(如數(shù)組和對(duì)象等),在需要復(fù)制內(nèi)存時(shí),將復(fù)雜對(duì)象分解為最小粒度來(lái)處理。這樣可以使內(nèi)存中復(fù)雜對(duì)象中某一部分做修改時(shí),不必將該對(duì)象的所有元素全部“分離復(fù)制”出一份內(nèi)存拷貝;

復(fù)制代碼 代碼如下:


array_fill()填充數(shù)組時(shí)也采用了COW的策略,可能會(huì)影響對(duì)本例的演示,感興趣的讀者可以 閱讀:$PHP_SRC/ext/standard/array.c中PHP_FUNCTION(array_fill)的實(shí)現(xiàn)。

xdebug_debug_zval()是xdebug擴(kuò)展中的一個(gè)函數(shù),用于輸出變量在zend內(nèi)部的引用信息。 如果你沒(méi)有安裝xdebug擴(kuò)展,也可以使用debug_zval_dump()來(lái)代替。 參考:http://www.php.net/manual/zh/function.debug-zval-dump.php


實(shí)現(xiàn)寫(xiě)時(shí)復(fù)制

        看完上面的三個(gè)例子,相信大家也可以了解到PHP中COW的實(shí)現(xiàn)原理: PHP中的COW基于引用計(jì)數(shù)ref_count和is_ref實(shí)現(xiàn),多一個(gè)變量指針,就將ref_count加1, 反之減去1,減到0就銷毀;同理,多一個(gè)強(qiáng)制引用&,就將is_ref加1,反之減去1。

這里有一個(gè)比較典型的例子:

復(fù)制代碼 代碼如下:


    $foo = 1;
    xdebug_debug_zval('foo');
    $bar = $foo;
    xdebug_debug_zval('foo');
    $bar = 2;
    xdebug_debug_zval('foo');
?>
//-----執(zhí)行結(jié)果-----
foo: (refcount=1, is_ref=0)=1
foo: (refcount=2, is_ref=0)=1
foo: (refcount=1, is_ref=0)=1


  經(jīng)過(guò)前面對(duì)變量章節(jié)的介紹,我們知道當(dāng)$foo被賦值時(shí),$foo變量的值的只由$foo變量指向。當(dāng)$foo的值被賦給$bar時(shí),PHP并沒(méi)有將內(nèi)存復(fù)制一份交給$bar,而是把$foo和$bar指向同一個(gè)地址。同時(shí)引用計(jì)數(shù)增加1,也就是新的2。隨后,我們更改了$bar的值,這時(shí)如果直接需該$bar變量指向的內(nèi)存,則$foo的值也會(huì)跟著改變。這不是我們想要的結(jié)果。于是,PHP內(nèi)核將內(nèi)存復(fù)制出來(lái)一份,并將其值更新為賦值的:2(這個(gè)操作也稱為變量分離操作),同時(shí)原$foo變量指向的內(nèi)存只有$foo指向,所以引用計(jì)數(shù)更新為:refcount=1。

        看上去很簡(jiǎn)單,但由于&運(yùn)算符的存在,實(shí)際的情形要復(fù)雜的多。見(jiàn)下面的例子:

PHP中寫(xiě)時(shí)復(fù)制的示例分析


圖6.6 &操作符引起的內(nèi)存復(fù)制分離>

從這個(gè)例子可以看出PHP對(duì)&運(yùn)算符的一個(gè)容易出問(wèn)題的處理:當(dāng) $beauty=&$pan; 時(shí),兩個(gè)變量本質(zhì)上都變成了引用類型,導(dǎo)致看上去的普通變量$pan, 在某些內(nèi)部處理中與&$pan行為相同,尤其是在數(shù)組元素中使用引用變量,很容易引發(fā)問(wèn)題。(見(jiàn)最后的例子)

       PHP的大多數(shù)工作都是進(jìn)行文本處理,而變量是載體,不同類型的變量的使用貫穿著PHP的生命周期,變量的COW策略也就體現(xiàn)了Zend引擎對(duì)變量及其內(nèi)存處理,具體可以參閱源碼文件相關(guān)的內(nèi)容:

復(fù)制代碼 代碼如下:


Zend/zend_execute.c
========================================
    zend_assign_to_variable_reference();
    zend_assign_to_variable();
    zend_assign_to_object();
    zend_assign_to_variable();

//以及下列宏定義的使用
Zend/zend.h
========================================
    #define Z_REFCOUNT(z)           Z_REFCOUNT_P(&(z))
    #define Z_SET_REFCOUNT(z, rc)       Z_SET_REFCOUNT_P(&(z), rc)
    #define Z_ADDREF(z)         Z_ADDREF_P(&(z))
    #define Z_DELREF(z)         Z_DELREF_P(&(z))
    #define Z_ISREF(z)          Z_ISREF_P(&(z))
    #define Z_SET_ISREF(z)          Z_SET_ISREF_P(&(z))
    #define Z_UNSET_ISREF(z)        Z_UNSET_ISREF_P(&(z))
    #define Z_SET_ISREF_TO(z, isref)    Z_SET_ISREF_TO_P(&(z), isref)


最后,請(qǐng)慎用引用&

       引用和前面提到的變量的引用計(jì)數(shù)和PHP中的引用并不是同一個(gè)東西,引用和C語(yǔ)言中的指針的類似,他們都可以通過(guò)不同的標(biāo)示訪問(wèn)到同樣的內(nèi)容,但是PHP的引用則只是簡(jiǎn)單的變量別名,沒(méi)有C指令的靈活性和限制。

      PHP中有非常多讓人覺(jué)得意外的行為,有些因?yàn)闅v史原因,不能破壞兼容性而選擇暫時(shí)不修復(fù),或者有的使用場(chǎng)景比較少。在PHP中只能盡量的避開(kāi)這些陷阱。例如下面這個(gè)例子。

      由于引用操作符會(huì)導(dǎo)致PHP的COW策略優(yōu)化,所以使用引用也需要對(duì)引用的行為有明確的認(rèn)識(shí)才不至于誤用,避免帶來(lái)一些比較難以理解的的Bug。如果您認(rèn)為您已經(jīng)足夠了解了PHP中的引用,可以嘗試解釋下面這個(gè)例子:

復(fù)制代碼 代碼如下:


$foo['love'] = 1;
$bar  = &$foo['love'];
$tipi = $foo;
$tipi['love'] = '2';
echo $foo['love'];


這個(gè)例子最后會(huì)輸出 2 , 大家會(huì)非常驚訝于$tipi怎么會(huì)影響到$foo,  $bar變量的引用操作,將$foo['love']污染變成了引用,從而Zend沒(méi)有對(duì)$tipi['love']的修改產(chǎn)生內(nèi)存的復(fù)制分離。

以上是“PHP中寫(xiě)時(shí)復(fù)制的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!


本文題目:PHP中寫(xiě)時(shí)復(fù)制的示例分析-創(chuàng)新互聯(lián)
文章地址:http://weahome.cn/article/deepcd.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部