問題引入
首先來看看PHP中的賦值與引用問題
<?php$a = 10;//將常量值賦給變數,會為a分配記憶體空間 $b = $a;//變數賦值給變數,是不是copy了一份副本,b也分配了記憶體空間呢? $c = &$a;//引用是不會為c分配空間的,c和a是共用一份空間的。?>
對於中間的那個問題,你的答案是什麼呢?在今天之前,我的答案是會為b分配記憶體空間。因為我是這麼理解的:
&賦值的時候,視為一個變數定義了一個別名,增加了一個對記憶體空間的引用。改變其中一個,會影響其他的引用。而使用unset()時,只是斷開了對變數記憶體空間的引用,記憶體空間不會釋放。
而 = 賦值則不同,它會重新開闢一份記憶體空間儲存原變數的副本。兩者之間的修改不會相互影響。
而下面的程式則印證了這一點:
<?php$a = 10; //將常量值賦給變數,會為a分配記憶體空間 $b = $a; //變數賦值給變數,是不是copy了一份副本,b也分配了記憶體空間呢? $c = &$a; //引用是不會為c分配空間的,c和a是共用一份空間的。 $a = 5;echo $c; //輸出5,因為a和c 是指向同一個記憶體空間echo PHP_EOL;echo $b; //由於b是副本,對a的操作不會影響b,輸出10?>
那如果
$b = $a;//之後a 和 b 都不做任何改變,保持一致
有這麼一個問題,如果 = 賦值之後,兩個變數都不曾改變,如果是兩份副本,豈不是太浪費記憶體?
PHP中實際上避免了這種情況。
PHP中將一個變數賦值給新變數時,不會立即為新變數分配記憶體空間,只是增加了對記憶體空間的引用。當原變數或者新變數作出任何改變時,才會為新變數 分配一塊記憶體空間。
<?php$a = 1;$b = $a; echo $a;//在此之前,b都是和a共用記憶體空間的。 $a = 2;//a作出了改變,此時b才會有自己的空間?>
每個php變數存在一個叫”zval”的變數容器中。一個zval變數容器,除了包含變數的類型和值,還包括兩個位元組的額外資訊。第一個是”is_ref”,是個bool值,用來標識這個變數是否是屬於引用集合(referenceset)。通過這個位元組,php引擎才能把普通變數和引用變數區分開來,由於php允許使用者通過使用&來使用自訂引用,zval變數容器中還有一個內部引用計數機制,來最佳化記憶體使用量。第二個額外位元組是”refcount”,用以表示指向這個zval變數容器的變數(也稱符號即symbol)個數。當”refcount”的值是1時,”is_ref”的值總是FALSE.
安裝xdebug之後,利用xdebug_debug_zval(),可以看到zval結構:
如下:
<?php$a = 1;$b = $a;echo $a;//在此之前,b都是和a共用記憶體空間的。xdebug_debug_zval('b');$a = 2;//a作出了改變,此時b才會有自己的空間xdebug_debug_zval('b');?>
輸出:
b: (refcount=2, is_ref=0), int 1b: (refcount=1, is_ref=0), int 1
由上面的結果可以看到,在a作出改變之前,引用計數是2 ,當a作出改變之後,b的引用計數變為1,是因為b重新分配了空間。
上面說描述的現象就是寫時複製。
寫時複製
寫時複製(Copy-on-Write,也縮寫為COW),顧名思義,就是在寫入時才真正複製一份記憶體進行修改。 COW最早應用在*nix系統中對線程與記憶體使用量的最佳化,後面廣泛的被使用在各種程式設計語言中,如C++的STL等。 在PHP核心中,COW也是主要的記憶體最佳化手段。 在前面關於變數和記憶體的討論中,引用計數對變數的銷毀與回收中起著至關重要的標識作用。 引用計數存在的意義,就是為了使得COW可以正常運作,從而實現對記憶體的最佳化使用。
寫時複製優點:是通過賦值的方式賦值給變數時不會申請新記憶體來存放新變數所儲存的值,而是簡單的通過一個計數器來共用記憶體,只有在其中的一個引用指向變數的值發生變化時才申請新空間來儲存值內容以減少對記憶體的佔用。
從PHP底層基礎資料結構來看
ref_count和is_ref是定義於zval結構體中;
is_ref標識是不是使用者使用 & 的強制引用;
ref_count是引用計數,用於標識此zval被多少個變數引用,即寫時複製的自動引用,為0時會被銷毀。
問題引入
首先來看看PHP中的賦值與引用問題
<?php$a = 10;//將常量值賦給變數,會為a分配記憶體空間 $b = $a;//變數賦值給變數,是不是copy了一份副本,b也分配了記憶體空間呢? $c = &$a;//引用是不會為c分配空間的,c和a是共用一份空間的。?>
對於中間的那個問題,你的答案是什麼呢?在今天之前,我的答案是會為b分配記憶體空間。因為我是這麼理解的:
&賦值的時候,視為一個變數定義了一個別名,增加了一個對記憶體空間的引用。改變其中一個,會影響其他的引用。而使用unset()時,只是斷開了對變數記憶體空間的引用,記憶體空間不會釋放。
而 = 賦值則不同,它會重新開闢一份記憶體空間儲存原變數的副本。兩者之間的修改不會相互影響。
而下面的程式則印證了這一點:
<?php$a = 10; //將常量值賦給變數,會為a分配記憶體空間 $b = $a; //變數賦值給變數,是不是copy了一份副本,b也分配了記憶體空間呢? $c = &$a; //引用是不會為c分配空間的,c和a是共用一份空間的。 $a = 5;echo $c; //輸出5,因為a和c 是指向同一個記憶體空間echo PHP_EOL;echo $b; //由於b是副本,對a的操作不會影響b,輸出10?>
那如果
$b = $a;//之後a 和 b 都不做任何改變,保持一致
有這麼一個問題,如果 = 賦值之後,兩個變數都不曾改變,如果是兩份副本,豈不是太浪費記憶體?
PHP中實際上避免了這種情況。
PHP中將一個變數賦值給新變數時,不會立即為新變數分配記憶體空間,只是增加了對記憶體空間的引用。當原變數或者新變數作出任何改變時,才會為新變數 分配一塊記憶體空間。
<?php$a = 1;$b = $a; echo $a;//在此之前,b都是和a共用記憶體空間的。 $a = 2;//a作出了改變,此時b才會有自己的空間?>
每個php變數存在一個叫”zval”的變數容器中。一個zval變數容器,除了包含變數的類型和值,還包括兩個位元組的額外資訊。第一個是”is_ref”,是個bool值,用來標識這個變數是否是屬於引用集合(referenceset)。通過這個位元組,php引擎才能把普通變數和引用變數區分開來,由於php允許使用者通過使用&來使用自訂引用,zval變數容器中還有一個內部引用計數機制,來最佳化記憶體使用量。第二個額外位元組是”refcount”,用以表示指向這個zval變數容器的變數(也稱符號即symbol)個數。當”refcount”的值是1時,”is_ref”的值總是FALSE.
安裝xdebug之後,利用xdebug_debug_zval(),可以看到zval結構:
如下:
<?php$a = 1;$b = $a;echo $a;//在此之前,b都是和a共用記憶體空間的。xdebug_debug_zval('b');$a = 2;//a作出了改變,此時b才會有自己的空間xdebug_debug_zval('b');?>
輸出:
b: (refcount=2, is_ref=0), int 1b: (refcount=1, is_ref=0), int 1
由上面的結果可以看到,在a作出改變之前,引用計數是2 ,當a作出改變之後,b的引用計數變為1,是因為b重新分配了空間。
上面說描述的現象就是寫時複製。
寫時複製
寫時複製(Copy-on-Write,也縮寫為COW),顧名思義,就是在寫入時才真正複製一份記憶體進行修改。 COW最早應用在*nix系統中對線程與記憶體使用量的最佳化,後面廣泛的被使用在各種程式設計語言中,如C++的STL等。 在PHP核心中,COW也是主要的記憶體最佳化手段。 在前面關於變數和記憶體的討論中,引用計數對變數的銷毀與回收中起著至關重要的標識作用。 引用計數存在的意義,就是為了使得COW可以正常運作,從而實現對記憶體的最佳化使用。
寫時複製優點:是通過賦值的方式賦值給變數時不會申請新記憶體來存放新變數所儲存的值,而是簡單的通過一個計數器來共用記憶體,只有在其中的一個引用指向變數的值發生變化時才申請新空間來儲存值內容以減少對記憶體的佔用。
從PHP底層基礎資料結構來看
ref_count和is_ref是定義於zval結構體中;
is_ref標識是不是使用者使用 & 的強制引用;
ref_count是引用計數,用於標識此zval被多少個變數引用,即寫時複製的自動引用,為0時會被銷毀。