什么是opcode
opcode(operate code)是計算機指令中的一部分,用於指定要執行的操作,指令的格式和規范由處理器的指定規范指定
opcode是一種php腳本編譯后的中間語言,就像java的ByteCode,或者.NET的MSL
為什么要使用opcode緩存
opcode cache的目的是避免重復編譯,減少CPU和內存開銷的。如果動態內容的性能瓶頸不在於CPU和內容,而在於IO操作,比如數據庫查詢帶來的IO開銷,這個時候opcode cache的性能提升是非常有局限的。無論如何既然opcode cache 可以降低cpu和內存的開銷,這當然是好事了
目前PHP中常見的opcode cahce模塊如下
APC
Optimizer+(目前已開源並與php5.5+集成了opcache)
xcache
eAccelerator
Opcode原理
例如有如下一段代碼
<?php echo 'Hello World'; $a = 1 + 1; echo $a; ?>
php執行這段代碼會經過如下4個步驟(准確的說,通過php的語言引擎Zend)
Scanning(Lexing)將php代碼轉化為語言片段(Tokens) Parsing,將Tokens轉化為簡單而有意義的表達式 Complilation,將表達式編譯成Opcode Execution,順序執行Opcode,每次一條,從而實現php腳本的功能
如下圖
Lexing階段
Lex 就是一個詞法分析的依據表。Zend引擎會會對輸入的php代碼進行詞法分析(切確的說是: Zend/zend_language_scanner.c會根據Zend/zend_language_scanner.l(Lex文件) ),從而得到一個一個的詞,php中提供了一個函數:token_get_all可以將一段php代碼解析成tokens
如果用這個函數分析上面的示例代碼,結果如下:
Array ( [0] => Array ( [0] => 374 [1] => <?php [2] => 1 ) [1] => Array ( [0] => 377 [1] => [2] => 1 ) [2] => Array ( [0] => 317 [1] => echo [2] => 1 ) [3] => Array ( [0] => 377 [1] => [2] => 1 ) [4] => Array ( [0] => 316 [1] => "Hello World" [2] => 1 ) [5] => ; [6] => Array ( [0] => 310 [1] => $a [2] => 1 ) [7] => Array ( [0] => 377 [1] => [2] => 1 ) [8] => = [9] => Array ( [0] => 377 [1] => [2] => 1 ) [10] => Array ( [0] => 306 [1] => 1 [2] => 1 ) [11] => Array ( [0] => 377 [1] => [2] => 1 ) [12] => + [13] => Array ( [0] => 377 [1] => [2] => 1 ) [14] => Array ( [0] => 306 [1] => 1 [2] => 1 ) [15] => ; [16] => Array ( [0] => 377 [1] => [2] => 1 ) [17] => Array ( [0] => 317 [1] => echo [2] => 1 ) [18] => Array ( [0] => 377 [1] => [2] => 1 ) [19] => Array ( [0] => 310 [1] => $a [2] => 1 ) [20] => ; [21] => Array ( [0] => 376 [1] => ?> [2] => 1 ) )
分析這個返回結果我們可以發現,源碼中的字符串,字符,空格都會原樣返回。每個源代碼的字符都會出現在相應的順序處。而其他的例如標簽,操作符,語句 都被轉化成一個包含;兩部分的array:Token ID(也就是在Zend內部的該Token的對應碼,比如T_ECHO,T_STRING)和 源碼中原來的內容
Parsing階段
Parsing階段首先會丟棄Tokens array中的多余空格,然后將剩余的Tokens轉換成一個一個簡單的表達式
echo a contanst string add two numbers together store the result of the prior expression to a variable echo a variable
Complilation階段
Complilation階段會把Tokens編譯成一個個op_array,每個op_array包含如下5個部分
Opcode數字的標示,指明了每個op_array的操作類型,比如add,echo 結果 存放Opcode的結果 操作數1 給Opcode的操作數 操作數2 擴展值 1個整形用來區別被重載的操作符
比如我的php代碼會被Parsing成:
ZEND_ECHO 'Hello World' ZEND_ADD ~0 1 1 ZEND_ASSIGN !0 ~0 ZEND_ECHI ~0
在上面的代碼我們並沒有看到 $a,去哪里了?
這個就要介紹操作數了,每個操作數都是由以下兩個部分組成:
op_type :為IS_CONST,IS_TMP_VAR,IS_VAR,IS_UNUESED or IS_CV
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了
Opcode Cache原理
通過上面的介紹,我們了解了opcode,關於opcode cache的原理圖大致如下
我們可以看到除了 Lexing,Parsing,Complilation,Execution階段 還多了一個階段:檢測文件是否有更新
如果沒有更新直接獲取緩存的opcode,直接進入Execution階段然后返回結果
如果更新了就按照原來流程(加入一個環節:圖中紅色部分 緩存opcode)
原文地址: 深入了解php opcode緩存原理
標簽: php opcache opcode apc token_get_all 緩存