詳解:PHP加速器配置神器opcache

安全劍客發表於2019-02-22
什麼是opcode?

當直譯器完成對指令碼程式碼的分析後,便將它們生成可以直接執行的中間程式碼,也稱為操作碼(Operate Code,opcode)。Opcode cache的目地是避免重複編譯,減少CPU和記憶體開銷。如果動態內容的效能瓶頸不在於CPU和記憶體,而在於I/O操作,比如資料庫查詢帶來的磁碟I/O開銷,那麼opcode cache的效能提升是非常有限的。但是既然opcode cache能帶來CPU和記憶體開銷的降低,這總歸是好事。

為什麼要使用opcode快取?

這得從PHP程式碼的生命週期說起,請求PHP指令碼時,會經過五個步驟,如下圖所示:
詳解:PHP加速器配置神器opcache詳解:PHP加速器配置神器opcache

Zend引擎必須從檔案系統讀取檔案、掃描其詞典和表示式、解析檔案、建立要執行的計算機程式碼(稱為Opcode),最後執行Opcode。每一次請求PHP指令碼都會執行一遍以上步驟,如果PHP原始碼沒有變化,那麼Opcode也不會變化,顯然沒有必要每次都重行生成Opcode,結合在Web中無所不在的快取機制,我們可以把Opcode快取下來,以後直接訪問快取的Opcode豈不是更快,啟用Opcode快取之後的流程圖如下所示:
詳解:PHP加速器配置神器opcache詳解:PHP加速器配置神器opcache

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, 將表示式編譯成Opocdes。
4)Execution, 順次執行Opcodes,每次一條,從而實現PHP指令碼的功能。
PHP opcache介紹

Optimizer+(Optimizer+於2013年3月中旬改名為Opcache),OPcache通過將PHP指令碼預編譯的位元組碼儲存到共享記憶體中來提升PHP的效能,儲存預編譯位元組碼的好處就是省去了每次載入和解析PHP指令碼的開銷。

PHP 5.5.0 及後續版本中已經繫結了 OPcache 擴充套件。 對於 PHP 5.2,5.3 和 5.4 版本可以使用 » PECL擴充套件中的OPcache庫。

PHP 5.5.0及後續版本

OPcache只能編譯為共享擴充套件。如果你使用–disable-all引數禁用了預設擴充套件的構建,那麼必須使用–enable-opcache選項來開啟OPcache。編譯之後,就可以使用 zend_extension 指令來將 OPcache 擴充套件載入到 PHP 中。

推薦的php.ini設定

使用下列推薦設定來獲得較好的效能:

opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1
opcache.save_comments=0

你也可以禁用 opcache.save_comments 並且啟用 opcache.enable_file_override。 需要提醒的是,在生產環境中使用上述配置之前,必須經過嚴格測試。 因為上述配置存在一個已知問題,它會引發一些框架和應用的異常, 尤其是在存在文件使用了備註註解的時候。

以下是opcache的配置說明,其中給有值得都是預設配置:

; opcache的開關,關閉時程式碼不再優化.
opcache.enable=1
 
; Determines if Zend OPCache is enabled for the CLI version of PHP
opcache.enable_cli=1
 
; OPcache的共享記憶體大小,以兆位元組為單位。總共能夠儲存多少預編譯的PHP程式碼(單位:MB)
; 推薦128
opcache.memory_consumption=64
 
; 用來儲存臨時字串的記憶體大小,以兆位元組為單位.
; 推薦8
opcache.interned_strings_buffer=4
 
; 最大快取的檔案數目200到100000之間.
; 推薦4000
opcache.max_accelerated_files=2000
 
; 記憶體"浪費"達到此值對應的百分比,就會發起一個重啟排程.
opcache.max_wasted_percentage=5
 
; 開啟這條指令, Zend Optimizer + 會自動將當前工作目錄的名字追加到指令碼鍵上,以此消除同名檔案間的鍵值命名衝突.關閉這條指令會提升效能,但是會對已存在的應用造成破壞.
opcache.use_cwd=0
 
; 開啟檔案時間戳驗證
opcache.validate_timestamps=1
 
; 檢查指令碼時間戳是否有更新的週期,以秒為單位。設定為0會導致針對每個請求,OPcache都會檢查指令碼更新.
; 推薦60
opcache.revalidate_freq=2
 
; 允許或禁止在include_path中進行檔案搜尋的優化.
opcache.revalidate_path=0
 
; 如果禁用,指令碼檔案中的註釋內容將不會被包含到操作碼快取檔案,這樣可以有效減小優化後的檔案體積,禁用此配置指令可能會導致一些依賴註釋或註解的應用或框架無法正常工作,比如:Doctrine,Zend Framework2等.
; 推薦0
opcache.save_comments=1
 
; 如果禁用,則即使檔案中包含註釋,也不會載入這些註釋內容。本選項可以和opcache.save_comments一起使用,以實現按需載入註釋內容.
opcache.load_comments=1
; 開啟快速關閉,開啟這個在PHP Request Shutdown的時候會收記憶體的速度會提高.
; 推薦1
opcache.fast_shutdown=1
 
; 允許覆蓋檔案存在(file_exists等)的優化特性.
opcache.enable_file_override=0 
 
; 定義啟動多少個優化過程.
opcache.optimization_level=0xffffffff
 
; 啟用此Hack可以暫時性的解決"can’t redeclare class"錯誤.
opcache.inherited_hack=1
 
; 啟用此Hack可以暫時性的解決"can’t redeclare class"錯誤.
;opcache.dups_fix=0
 
; 通過檔案大小屏除大檔案的快取,預設情況下所有的檔案都會被快取.
;opcache.max_file_size=0
 
; 每N次請求檢查一次快取校驗.預設值0表示檢查被禁用了,由於計算校驗值有損效能,這個指令應當緊緊在開發除錯的時候開啟.
;opcache.consistency_checks=0
 
; 從快取不被訪問後,等待多久後(單位為秒)排程重啟.
;opcache.force_restart_timeout=180
 
; 日誌記錄level,預設只有fatal error和error.
;opcache.error_log=
 
; 將錯誤資訊寫入到伺服器(Apache等)日誌
;opcache.log_verbosity_level=1
 
; 記憶體共享的首選後臺.留空則是讓系統選擇.
;opcache.preferred_memory_model=
 
; 執行php指令碼時保護共享記憶體防止意外的寫入,只對debug時有用.
;opcache.protect_memory=0

最後說一下使用opcache加速php時應該注意的坑:

opcache依靠的是PHP檔案的modify time作為檔案被修改的檢測條件,基於這個會引發兩個問題。

第一個問題是做版本回滾時,由於版本回滾後的檔案修改時間比現有opcache快取的檔案時間要往前一些,所以可能會導致opcache不會清除快取,需要手動reload。

第二個問題是做版本釋出時,一般都是sync方式,可能會出現檔案釋出一半時被opcache快取,使用者訪問會報程式錯誤,這個主要是因為檔案內容快取了一半,但是檔案的時間戳不會在改變,所以就算opcache檢測時也不會去讀取新的檔案了,需要手動reload。

針對這兩個問題,不光reload可以解決,同樣呼叫opcache的介面也可以清除opcache快取。

你可以使用opcache_reset()或者或者opcache_invalidate()函式來手動重置OPcache。

opcache_reset():該函式將重置整個位元組碼快取,在呼叫opcache_reset()之後,所有的指令碼將會重新載入並且在下次被點選的時候重新解析。

opcache_invalidate():該函式的作用是使得指定指令碼的位元組碼快取失效。 如果force沒有設定或者傳入的是FALSE,那麼只有當指令碼的修改時間 比對應位元組碼的時間更新,指令碼的快取才會失效。

但是不推薦使用,個人在生產環境中進行程式碼釋出後呼叫opcache_reset()清空快取(測試確實可以清空快取),出現過奇葩問題(訪問量大的應用),後來就果斷放棄了,使用了reload的方式。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559985/viewspace-2636653/,如需轉載,請註明出處,否則將追究法律責任。

相關文章