什麼是opcode緩存?
當解釋器完成對腳本代碼的分析後,便將它們生成能夠直接運行的中間代碼,也稱爲操做碼(Operate Code,opcode)。Opcode cache的目地是避免重複編譯,減小CPU和內存開銷。若是動態內容的性能瓶頸不在於CPU和內存,而在於I/O操做,好比數據庫查詢帶來的磁盤I/O 開銷,那麼opcode cache的性能提高是很是有限的。可是既然opcode cache能帶來CPU和內存開銷的下降,這總歸是好事。php
現代操做碼緩存器(Optimizer+,APC2.0+,其餘)使用共享內存進行存儲,而且能夠直接從中執行文件,而不用在執行前「反序列化」代碼。這將帶來顯着的性能加速,一般下降了總體服務器的內存消耗,並且不多有缺點。html
爲何要使用Opcode緩存?
這得從PHP代碼的生命週期提及,請求PHP腳本時,會通過五個步驟,以下圖所示:linux
Zend引擎必須從文件系統讀取文件、掃描其詞典和表達式、解析文件、建立要執行的計算機代碼(稱爲Opcode),最後執行Opcode。每一次 請求PHP腳本都會執行一遍以上步驟,若是PHP源代碼沒有變化,那麼Opcode也不會變化,顯然沒有必要每次都重行生成Opcode,結合在Web中 無所不在的緩存機制,咱們能夠把Opcode緩存下來,之後直接訪問緩存的Opcode豈不是更快,啓用Opcode緩存以後的流程圖以下所示:數據庫
有那些PHP opcode緩存插件?
Optimizer+(Optimizer+於2013年3月中旬更名爲Opcache,PHP 5.5集成Opcache,其餘的會不會消失?)、eAccelerator、xcache、APC …express
PHP opcode原理:
Opcode是一種PHP腳本編譯後的中間語言,就像Java的ByteCode,或者.NET的MSL,舉個例子,好比你寫下了以下的PHP代碼:緩存
<?php echo "Hello World"; $a =1+1; echo $a;?>
PHP執行這段代碼會通過以下4個步驟(確切的來講,應該是PHP的語言引擎Zend)服務器
1.Scanning(Lexing),將PHP代碼轉換爲語言片斷(Tokens)2.Parsing,將Tokens轉換成簡單而有意義的表達式3.Compilation,將表達式編譯成Opocdes4.Execution,順次執行Opcodes,每次一條,從而實現PHP腳本的功能。
題外話:如今有的Cache好比APC,可使得PHP緩存住Opcodes,這樣,每次有請求來臨的時候,就不須要重複執行前面3步,從而能大幅的提升PHP的執行速度。函數
那什麼是Lexing? 學過編譯原理的同窗都應該對編譯原理中的詞法分析步驟有所瞭解,Lex就是一個詞法分析的依據表。 Zend/zend_language_scanner.c會根據Zend/zend_language_scanner.l(Lex文件),來輸入的 PHP代碼進行詞法分析,從而獲得一個一個的「詞」,PHP4.2開始提供了一個函數叫token_get_all,這個函數就能夠講一段PHP代碼 Scanning成Tokens;性能
若是用這個函數處理咱們開頭提到的PHP代碼,將會獲得以下結果:優化
Array([0]=>Array([0]=>367[1]=>Array([0]=>316[1]=> echo )[2]=>Array([0]=>370[1]=>)[3]=>Array([0]=>315[1]=>"Hello World")[4]=>;[5]=>Array([0]=>370[1]=>)[6]=>=[7]=>Array([0]=>370[1]=>)[8]=>Array([0]=>305[1]=>1)[9]=>Array([0]=>370[1]=>)[10]=>+[11]=>Array([0]=>370[1]=>)[12]=>Array([0]=>305[1]=>1)[13]=>;[14]=>Array([0]=>370[1]=>)[15]=>Array([0]=>316[1]=> echo )[16]=>Array([0]=>370[1]=>)[17]=>;)
分析這個返回結果咱們能夠發現,源碼中的字符串,字符,空格,都會原樣返回。每一個源代碼中的字符,都會出如今相應的順序處。而,其餘的好比標籤,操 做符,語句,都會被轉換成一個包含倆部分的Array: Token ID (也就是在Zend內部的改Token的對應碼,好比,T_ECHO,T_STRING),和源碼中的原來的內容。
接下來,就是Parsing階段了,Parsing首先會丟棄Tokens Array中的多於的空格,而後將剩餘的Tokens轉換成一個一個的簡單的表達式
1.echo a constant string 2.add two numbers together 3.store the result of the prior expression to a variable 4.echo a variable
而後就改Compilation階段了,它會把Tokens編譯成一個個op_array, 每一個op_arrayd包含以下5個部分:
1.Opcode數字的標識,指明瞭每一個op_array的操做類型,好比add , echo 2.結果存放Opcode結果3.操做數1給Opcode的操做數4.操做數25.擴展值1個整形用來區別被重載的操做符
好比,咱們的PHP代碼會被Parsing成:
* ZEND_ECHO 'Hello World'* ZEND_ADD ~011* ZEND_ASSIGN !0~0* ZEND_ECHO !0
你可能會問了,咱們的$a去那裏了?
這個要介紹操做數了,每一個操做數都是由如下倆個部分組成:
a)op_type :爲IS_CONST, IS_TMP_VAR, IS_VAR, IS_UNUSED, or IS_CV b)u,一個聯合體,根據op_type的不一樣,分別用不一樣的類型保存了這個操做數的值(const)或者左值(var)
而對於var來講,每一個var也不同
IS_TMP_VAR, 顧名思義,這個是一個臨時變量,保存一些op_array的結果,以便接下來的op_array使用,這種的操做數的u保存着一個指向變量表的一個句柄(整數),這種操做數通常用~開頭,好比~0,表示變量表的0號未知的臨時變量
IS_VAR 這種就是咱們通常意義上的變量了,他們以$開頭表示
IS_CV 表示ZE2.1/PHP5.1之後的編譯器使用的一種cache機制,這種變量保存着被它引用的變量的地址,當一個變量第一次被引用的時候,就會被CV起來,之後對這個變量的引用就不須要再次去查找active符號表了,CV變量以!開頭表示。
這麼看來,咱們的$a被優化成!0了。