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