如何更好的使用OPcache實現效能優化

奕鵬發表於2021-09-25

專注於PHP、MySQL、Linux和前端開發,感興趣的感謝點個關注喲!!!文章整理在GitHub,Gitee。主要包含的技術有PHP、Redis、MySQL、JavaScript、HTML&CSS、Linux、Java、Golang、Linux和工具資源等相關理論知識、面試題和實戰內容。

文章說明

一直知道opcache可以提高PHP效能,但沒有具體的關注,更多的利用其他的元件來提升系統的效能。一次無意開啟了opcache之後,並隨意設定了一些配置。結果導致後面在使用一個專案時,發現專案總是不會讀取到最新的程式碼,而是隔一段時間才會執行到最新程式碼。排查了很久才想起來開啟了opcache,於是對opcache做了一個簡單的學習與總結。

發現這個優化小技巧之後,後面也會對稍微底層進行探索學習,歡迎大家持續關注該文。

什麼是opcache

OPcache 通過將 PHP 指令碼預編譯的位元組碼儲存到共享記憶體中來提升 PHP 的效能, 儲存預編譯位元組碼的好處就是 省去了每次載入和解析 PHP 指令碼的開銷。

opcache執行原理

不使用opcache

在使用opcache之前,我們事先看一個request,PHP的一個大致處理流程是如何的。大致的示意圖如下:
Snipaste_2021-09-24_23-18-32

  1. 首先會去模組初始化一次,也就是載入我們php.ini當中的一些配置資訊,這裡需要根據配置資訊初始化一次。

  2. 初始化完php.ini的配置資訊之後,第二步就是針對當前請求的資訊做一次初始化。例如我的一些get、post以及$_SEVER等相關的資訊。

  3. 得到上面1和2中的資訊之後,則時候就會去真正執行我們的php指令碼檔案內容了,也就是我們寫的程式碼。是怎麼去實現的呢?如下圖:
    1128628-20180504142714761-711951956
    Zend引擎讀取.php檔案–>掃描其詞典和表示式 –>解析檔案–>建立要執行的計算機程式碼(稱為Opcode)–>最後執行Opcode–> response 返回。

  4. 執行完php指令碼檔案內容之後,這時候會針對1和2中的一些初始化資訊,進行銷燬。

使用opcache

當使用opcache之後,當一個請求來了之後,依然的會去執行上面提到的1和2,進行模組和請求的初始化。接著就會去編譯php指令碼檔案內容,opcache也是在這一個階段才會產生作用。

通過上面的第3步,我們可以看到每一次請求都會去解析php檔案內容,不管是php檔案的內容是否發生變化,都會執行這樣的一個重複流程來生成opcode。

opcache的作用就是減少每次請求都會去編譯php指令碼檔案,第一次將編譯好的指令碼檔案內容快取起來,下一次請求就不需要去重複編譯了,而是直接衝記憶體中取就行了。減少了CPU和記憶體的消耗。

Snipaste_2021-09-24_23-18-16

  1. 首先會去模組初始化一次,也就是載入我們php.ini當中的一些配置資訊,這裡需要根據配置資訊初始化一次。

  2. 初始化完php.ini的配置資訊之後,第二步就是針對當前請求的資訊做一次初始化。例如我的一些get、post以及$_SEVER等相關的資訊。

  3. 此時去解析php指令碼檔案,首先會去判斷opcode是否存在,如果不存在就執行一個編譯流程並快取到共享記憶體中。當存在opcode時,則直接使用共享記憶體中的opcode,不會再進行一次編譯的過程。
    1128628-20180504142702126-1584014725

  4. 執行完php指令碼檔案內容之後,這時候會針對1和2中的一些初始化資訊,進行銷燬。

使用總結

  1. 通過上面的對比,很容易看得出來opcache執行的時段在於編譯php指令碼檔案,減少了編譯的過程。

  2. 對於模組初始化、請求初始化等這樣的一個重複流程,該如何優化。這裡可以去了解一下swoole。

  3. 可能會存在這樣一個疑問,opcode給快取起來了,如果我們更新了程式碼,這時候還是會載入舊的opcode還是重新編譯一次opcode並快取起來呢?後面我們會單獨總結。

opcache配置說明

[opcache]
; 是否快開啟opcache快取。
;opcache.enable=1

; 是否在cli模式下開啟opcache。
;opcache.enable_cli=1

; opcache共享記憶體的大小(單位是M);opcache.memory_consumption=128

; 預留字串的的記憶體大小(單位是M);opcache.interned_strings_buffer=8

; 在hash表中儲存的最大指令碼檔案數量,範圍是2001000000之間。實際的情況是在{ 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987 }中找到第一個大於等於設定值的質數。最小範圍是200;opcache.max_accelerated_files=10000

; 浪費記憶體的上線,如果超過這個上線,opcache將重新啟動。
;opcache.max_wasted_percentage=5

; 如果啟用,opcache將會在hash表的指令碼鍵後面增加一個檔案目錄,避免吃同名的指令碼產生衝突。禁用的話可以提高效能,但是也容易導致應用不可用。
;opcache.use_cwd=1

; 如果啟用(1),opcache會每隔設定的值時間來判斷指令碼是否更新。如果禁用(0),則不會自動檢測指令碼更新,必須通過重啟PHP服務,或者使用opcache_reset()opcache_invalidate()函式來重新整理快取。
;opcache.validate_timestamps=1

; opcache檢查指令碼是否更新的時間週期(單位是秒),如果設定為0則會針對每一個請求進行檢查更新,如果validate_timestamps=0,該值不會生效。
;opcache.revalidate_freq=60

; 如果禁用,在統一include_path下面已經快取的檔案將被重用,因此無法找到該路徑下的同名檔案。
;opcache.revalidate_path=0

; 是否儲存PHP指令碼中的註釋內容。禁用,則不會快取PHP程式碼中的註釋,可以減少檔案中的體積,但是一些依賴註釋或者註解將無法使用。
;opcache.save_comments=1

; 如果啟用,則會使用快速停止續發事件。 所謂快速停止續發事件是指依賴 Zend 引擎的記憶體管理模組 一次釋放全部請求變數的記憶體,而不是依次釋放每一個已分配的記憶體塊。
; 在php7.2.0開始,被移除,這類說的事件將會在PHP中自動處理。
;opcache.fast_shutdown=1

; 如果啟用,在呼叫file_exists()is_file()is_readable()函式時,不管檔案是否被快取,都會檢測操作碼。如果禁用,可能讀取的內容是一些舊資料。
;opcache.enable_file_override=0

; 控制優化級別,是一個二進位制的位的掩碼。
;opcache.optimization_level=0xffffffff

; 不進行編譯優化的配置檔案路徑。該檔案中配置具體哪些不被編譯的檔案。如果文中每行的開頭是";"開頭,則會被視為註釋。黑名單中的檔名,可以是萬用字元,也可以使用字首。
; 例如配置檔案的路徑是"/home/blacklist.txt",則該配置的值就是該路徑。
; 配置的內容可以是如下格式

; 這是一段註釋,在解析的時候因為開頭是;,則會被視為註釋
;/var/www/a.php
;/var/www/a/b.php

;opcache.blacklist_filename=

; 以位元組為單位的快取的檔案大小上限。設定為 0 表示快取全部檔案。
;opcache.max_file_size=0

; 每個N次請求會檢查快取校驗和,0是不檢查。該項對效能有較大影響,儘量在除錯環境中使用。
;opcache.consistency_checks=0

; 如果快取處於非啟用狀態,等待多少秒之後計劃重啟。 如果超出了設定時間,則 OPcache 模組將殺除持有快取鎖的程式, 並進行重啟。
;opcache.force_restart_timeout=180

; 錯誤日誌檔案位置,不填寫將預設輸出到伺服器的錯誤日誌檔案中。
;opcache.error_log=

; 錯誤日誌檔案等級。
; 預設情況下,僅有致命級別(0)及錯誤級別(1)的日誌會被記錄。 其他可用的級別有:警告(2),資訊(3)和除錯(4)。
; 如何設定的是1以上,在進行force_restart_timeout選項時,會將錯誤日誌中插入一條警告資訊。
;opcache.log_verbosity_level=1

; opcache首選的記憶體模組,不配置則自動選擇。可以選擇的值有mmap,shm, posix 以及 win32。
;opcache.preferred_memory_model=

; 保護共享記憶體,以避免執行指令碼時發生非預期的寫入。 僅用於內部除錯。
;opcache.protect_memory=0

; 只允許指定字串開頭的PHP指令碼呼叫opcache api函式,預設不做限制。
;opcache.restrict_api=

; 在 Windows 平臺上共享記憶體段的基地址。 所有的 PHP 程式都將共享記憶體對映到同樣的地址空間。 使用此配置指令避免“無法重新附加到基地址”的錯誤。
;opcache.mmap_base=

; 配置二級快取目錄並啟用二級快取。 啟用二級快取可以在 SHM 記憶體滿了、伺服器重啟或者重置 SHM 的時候提高效能。 預設值為空字串 "",表示禁用基於檔案的快取。
;opcache.file_cache=

; 啟用或禁用在共享記憶體中的 opcode 快取。
;opcache.file_cache_only=0

; 當從檔案快取中載入指令碼的時候,是否對檔案的校驗和進行驗證。
;opcache.file_cache_consistency_checks=1

; 在 Windows 平臺上,當一個程式無法附加到共享記憶體的時候, 使用基於檔案的快取。需要開啟opcache.file_cache_only選項。建議開啟此選項,否則可能導致程式無法啟動。
;opcache.file_cache_fallback=1

; 啟用或者禁用將 PHP 程式碼(文字段)拷貝到 HUGE PAGES 中。 此項配置指令可以提高效能,但是需要在 OS 層面進行對應的配置。
;opcache.huge_code_pages=1

; 針對當前使用者,驗證快取檔案的訪問許可權。
;opcache.validate_permission=0

; 在 chroot 的環境中避免命名衝突。 為了防止程式訪問到 chroot 環境之外的檔案,應該在 chroot 的情況下啟用這個選項。
;opcache.validate_root=0

配置示例

下面這一段程式碼是PHP官方給的一個示例配置,推薦使用該配置項進行配置,也可以根據自己實際的情況進行單獨配置。

;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

問題總結

  1. 如何更新opcode?

編譯好的opcode會新增到共享記憶體中,如果我們更新了程式碼就需要去更新opcode,否則得到的程式碼還是舊的opcode。就會發生文章開頭說到的情況。要解決這個問題,我們有幾種方式。

; 方法一
直接重啟我們的php程式,但這樣會導致服務中斷,是一種不推薦的方式。

; 方法二
根據官方給出的函式,進行設定。在程式碼中使用opcache_reset()或者使用opcache_invalidate()函式進行充值opcode。直接通過一個特殊的連結去執行這個函式即可。

; 方法三
使用php.ini中的配置項實現自動充值opcode。
opcache.validate_timestamps = 1
opcache.revalidate_freq  = 60

效果演示

Snipaste_2021-09-25_12-42-05

Snipaste_2021-09-25_12-41-15

  1. 上面的兩張圖,第一張是未開啟opcache的一個壓測,第二個是開啟opcache的一個壓測。

  2. 從截圖上來看,開啟opcache開啟之後,有一些小幅度的提升。也並沒有網上說的翻倍的提升。

  3. 這裡的提升不能說opcache的提升效果不明顯,這需要根據綜合因素決定,這裡的演示使用Mac操作本身就會降低很多。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
喜歡的,可以關注公眾號"卡二條的技術圈"。

相關文章