PHP7的变化
引言
PHP7與PHP5版本相比有非常大的變化,尤其是在Zend引擎方面。為了提升性能,PHP7對(duì)Zend進(jìn)行了深度優(yōu)化,使得PHP的運(yùn)行速度大大提高,比PHP5.0-5.6快了近5倍,同時(shí)還降低了PHP對(duì)系統(tǒng)資源的占用。
PHP7比較大的變化有:
抽象語(yǔ)法樹(shù)
PHP是一種解釋性語(yǔ)言,通過(guò)解析器來(lái)執(zhí)行。
那么首先來(lái)看一下編譯器與解釋器的區(qū)別:讀入源語(yǔ)言后,解釋器和編譯器都要進(jìn)行詞法分析、語(yǔ)法分析和語(yǔ)義分析,之后,二者開(kāi)始有所分別。
解析器與編譯器的區(qū)別:
- 解釋器在語(yǔ)義分析后選擇了直接執(zhí)行語(yǔ)句;
編譯器在語(yǔ)義分析后選擇將將語(yǔ)義存儲(chǔ)成某一種中間語(yǔ)言,之后通過(guò)不同的后端翻譯成不同的機(jī)器語(yǔ)言(可執(zhí)行程序)。其存在一個(gè)預(yù)編譯的過(guò)程。如下圖所示:
PHP7之前的版本,代碼解釋過(guò)程:
- PHP代碼在語(yǔ)法解析階段直接生成ZendVM指令,即在zend_language_parser.y中直接生成opline指令,使得編譯器與執(zhí)行器耦合在一起。
編譯生成的指令再供執(zhí)行引擎使用,該指令是在語(yǔ)法指令直接生成的,若要更換執(zhí)行引擎,怎需要修改語(yǔ)法解析規(guī)則;若PHP語(yǔ)法變化,但沒(méi)有修改執(zhí)行引擎,仍需要修改語(yǔ)法解析規(guī)則。其代碼解析過(guò)程如下圖:
PHP7的代碼解析過(guò)程:
Native TLS
PHP5.x版本擴(kuò)展中,有TSRM_CC、TSRM_DC宏,用于線程安全。
PHP中有很多變量需要在不同函數(shù)間共享,多線程的環(huán)境下不能簡(jiǎn)單地通過(guò)全局變量來(lái)實(shí)現(xiàn),為了適應(yīng)線程的應(yīng)用環(huán)境,PHP提供了一個(gè)線程安全資源管理器,將全局資源進(jìn)行線程隔離,不同的線程之間互不干擾。
使用全局資源需要先獲取本線程的資源池,這個(gè)過(guò)程比較占用時(shí)間,因此,PHP5.x通過(guò)參數(shù)傳遞的方式將本線程的資源池傳遞給其他函數(shù),避免重復(fù)查找。這種方式需要所有函數(shù)接受資源池的參數(shù)(TSRM_DC宏所加的參數(shù)),這些參數(shù)傳遞不僅易遺漏參數(shù),還是得代碼不優(yōu)雅。
PHP7使用Native TLS(線程局部存儲(chǔ))來(lái)保存線程的資源池,簡(jiǎn)單來(lái)說(shuō)就是通過(guò)__thread標(biāo)識(shí)一個(gè)全局變量,這樣這個(gè)全局變量就是線程獨(dú)享的了,不同線程的修改不會(huì)相互影響。
指定函數(shù)參數(shù)、返回值類(lèi)型
輸入和輸出參數(shù)必須是指定的數(shù)據(jù)類(lèi)型,示例如下:
| 1 2 3 | function foo(string $name): array { return []; } |
zval結(jié)構(gòu)的變化
Zval是PHP中最重要的數(shù)據(jù)結(jié)構(gòu)之一(另一個(gè)比較重要的數(shù)據(jù)結(jié)構(gòu)是hash table),它包含了PHP中的變量值和類(lèi)型的相關(guān)信息。它是一個(gè)struct,在PHP5.x中,基本結(jié)構(gòu)為:
| 1 2 3 4 5 6 7 | struct _zval_struct { zvalue_value value; /* value,變量的具體值 */ zend_uint refcount__gc; /* variable ref count,記錄變量的引用計(jì)數(shù)(自動(dòng)回收的基礎(chǔ)) */ zend_uchar type; /* active type ,類(lèi)型*/ zend_uchar is_ref__gc; /* if it is a ref variable,標(biāo)識(shí)變量是否為引用 */ }; typedef struct _zval_struct zval; |
變量的實(shí)際值,具體來(lái)說(shuō)是一個(gè)zvalue_value的聯(lián)合體(union),用來(lái)適配不同的變量類(lèi)型:
| 1 2 3 4 5 6 7 8 9 10 | typedef union _zvalue_value { long lval; /* long value */ double dval; /* double value */ struct { /* string */ char *val; int len; } str; HashTable *ht; /* hash table value,used for array */ zend_object_value obj; /* object */ } zvalue_value; |
參考:PHP內(nèi)核探索之變量(1)變量的容器-Zval
PHP5.x中引用計(jì)數(shù)是在zval中,而不是在具體的value中,這樣一來(lái),導(dǎo)致變量復(fù)制時(shí)需要復(fù)制兩個(gè)結(jié)構(gòu),zval,zvalue_value始終是綁定在一起的。
- 變化1:PHP7中將引用計(jì)數(shù)轉(zhuǎn)移到具體的value中,這樣更合理,因?yàn)閦val只是變量的一個(gè)載體,可以簡(jiǎn)單地認(rèn)為是變量名,而value才是真正的值,這個(gè)改變使得PHP變量之間的復(fù)制、傳遞更加簡(jiǎn)潔、易懂。
- 變化2:zval結(jié)構(gòu)的大小由24byte減少到16byte,這PHP7能夠降低系統(tǒng)的資源占用。
異常處理
PHP5.x中很多操作會(huì)直接拋出error錯(cuò)誤,PHP7中將多數(shù)錯(cuò)誤改為了異常拋出,這樣就可以通過(guò)try catch捕捉到異常。這種新的異常處理方式使得錯(cuò)誤處理更加可控。
HashTable的變化
HashTable,即哈希表,也稱為散列表,它是PHP強(qiáng)大的array()類(lèi)型的內(nèi)部實(shí)現(xiàn)結(jié)構(gòu),也是內(nèi)核中使用非常頻繁的一個(gè)結(jié)構(gòu),函數(shù)符號(hào)表、類(lèi)符號(hào)表、常量符號(hào)表等都是通過(guò)HashTable實(shí)現(xiàn)的。
PHP7中,HashTable結(jié)構(gòu)的大小由72byte減小到56byte,同時(shí),數(shù)組元素Bucket結(jié)構(gòu)也由72byte減小到32byte。
執(zhí)行器
execute_data、opline采用寄存器變量存儲(chǔ),執(zhí)行器的調(diào)度函數(shù)為execute_ex(),這個(gè)函數(shù)復(fù)制執(zhí)行PHP代碼編譯生成ZendVM指令。在執(zhí)行期間會(huì)頻繁地用到execute_data、opline兩個(gè)變量。
PHP5.x中,這兩個(gè)變量是由execute_ex()通過(guò)參數(shù)傳遞給各指令handle的。
PHP7中不再采用傳參的方式,而是將execute_data、opline通過(guò)寄存器來(lái)存儲(chǔ),避免傳參導(dǎo)致的頻繁出入棧操作,同時(shí)寄存器相比于內(nèi)存的訪問(wèn)速度更快。這個(gè)優(yōu)化使得PHP的性能有了5%左右的提升。
新的參數(shù)解析
PHP5.x通過(guò)zend_parse_parameters()解析函數(shù)的參數(shù),PHP7提供另外一種方式,同時(shí)保留原來(lái)方式,但是新的解析方式速度更快。
參考:秦朋 《PHP7內(nèi)核剖析》第1.3節(jié)
推薦文章——關(guān)于PHP內(nèi)部實(shí)現(xiàn)的文章:
總結(jié)
- 上一篇: Spark --jars 依赖包的优先级
- 下一篇: PHP内核——内存管理