1. 程式人生 > >深入理解PHP7核心之Reference

深入理解PHP7核心之Reference

問題

上一章說過引用(REFERENCE)在PHP5的時候是一個標誌位, 而在PHP7以後我們把它變成了一種新的型別:IS_REFERNCE. 然而引用是一種很常見的應用, 所以這個變化帶來了很多的變化, 也給我們在做PHP7開發的時候, 因為有的時候疏忽忘了處理這個型別, 而帶來不少的bug.

最簡單的情況, 就是在處理各種型別的時候, 從此以後我們要多考慮這種新的型別, 比如在PHP7中, 這樣的程式碼形式就變得很常見了:

try_again:
swtich (Z_TYPE_P(zv)) {
	case IS_TRING:
	break;
	case IS_ARRAY:
	break;
    ...
	case IS_REFERENCE:
	zv = Z_REFVAL_P(zv); //解引用
	goto try_again;
	break;
}

如果大家自己寫的擴充套件, 如果忘了考慮這種新的型別, 那麼就會導致問題.

為什麼?

那麼既然這種新型別會帶來這麼多問題, 那麼當時為什麼要用把引用變成一種型別呢? 為什麼不還是使用一個標誌位呢?

一句話來說, 就是我們不得不這麼做. -_#

前面說到, Hashtable直接儲存的是zval, 這樣在符號表中, 倆個zval如何共用一個數值呢? 對於字串等複雜型別來說還好, 我們貌似可以在zend_refcounted結構中加入一個標誌位來表明是引用來解決, 然而這個也會遇到Change On Write帶來的複製, 但是我們知道在PHP7中, 一些型別是直接儲存在zval中的, 比如IS_LONG, 但是引用型別是需要引用計數的, 那麼對於一個是IS_LONG並且又是IS_REFERNCE的zval該如何表示呢?

為此, 我們創造了這個新的型別:

如圖所示, 引用是一種新的型別:zend_reference, 對於IS_REFERNCE型別的zval, zval.value.ref是一個指向zend_reference的指標, 它包含了引用計數和一個zval, 具體的zval的值是存在zval.value.ref->val中的.

所以對於IS_LONG的引用來說, 就用一個型別是IS_REFERNCE的zval, 它指向一個zend_reference, 而這個zend_reference->val中是一個型別為IS_LONG的zval.

Change On Write

PHP採用引用計數來做簡單的垃圾回收, 考慮如下的程式碼:

<?php
1. $val = "laruence";
2. $ref = &$val;
3. $copy = $val;
?>

$ref和$val是指向同一個zval的引用, 在PHP5的時候, 我們是通過一個引用計數為2, 並且引用標誌位為1來表示這種情況, 當把$val複製給$copy(line 3)的時候, 我們發現$val是一個計數大於1的引用, 所以要產生Change on write, 也就是分離. 所以我們需要複製這個zval.

而在PHP7中, 情況就變得簡單了很多, 首先在引用賦值給$ref(line 2)的時候, 生成一個IS_REFERNCE型別, 然後因為此時有倆個變數引用它所以zend_reference這個結構的引用計數zval.value.ref->gc.refcount為2.

再隨後的賦值給$copy(line 3)的時候, 發現$val是一個引用, 於是讓$copy指向的是zval.value.ref->val, 也就是字串值為laruence的zval, 然後把zval的引用計數+1, 也就是zval.value.ref->val.value.str.gc.refcount為2. 並沒有產生複製.

從而這就很好的解決了上一章所說的PHP5的那個經典的問題, 比如我們在PHP7下執行上一章的那個問題, 我們得到的結果是:

$ php-7.0/sapi/cli/php /tmp/1.php
Used 0.00021380008539
Used 0.00020173048281

可見確實沒有發生複製, 從而不會產生任何的效能問題.