iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 15
3
Modern Web

成為 Modern PHPer系列 第 15

Day 15:PHP 語言快取機制

前言

PHP 曾經存在各式各樣的語言層面快取機制,從 PHP 5.5 之後在編譯時就以 Zend Opcache 為預設的快取機制,從此也算是一統天下。

註:在官方宣佈不維護 APC 之後,有人跳出來做了 APCu,這到目前仍是可以使用的
註:composer 的最佳化中的第二種方式即是使用 APCu 進行快取,但它會與 --classmap-authoritative 衝突

前置知識

PHP 直譯流程

PHP 7 對其語言的直譯做了大幅度變化,以下將 PHP 5 與 7 分開說明。

不過對於 Zend VM 而言,它最終執行的都是 opcodes:可以將其想像是 JVM 執行 bytecode 那樣的模式。

PHP 5

從 PHP Source Code 到 opcodes 通常是經過以下流程

  • Lexical Analysis:詞法分析
    • 這個階段會根據語言規則分析 PHP 檔案中的關鍵字
    • 詳細的詞法分析可以去看龍書(懶得講)
    • 如果在這邊發現無法分析的詞彙,表示 compiler error。
    • 這邊將會把 PHP 檔案轉為 Token Steam
  • Parse:語法分析
    • 這個階段把 Lexical Analysis 的 Token Stream 解析為 opcode array

之後把 opcode 丟進 Zend VM 中執行。

PHP 7

PHP 7 的 Lexical Analysis 並沒有太大改變,但 Parse 階段不再直接解析為 opcode array。

  • Parse
    • 這個階段把 Lexical Analysis 的 Token Stream 轉為 Abstract Syntax Tree(AST)
  • Compile
    • 將 AST 的結果轉為 opcode arrays

AST 的存在可以將解析器與編譯器分離,使其可以做到一些原本難以做到的語法,詳細可以參閱 AST 的 RFC

Opcache 的功能

Zend Opcache 是利用將(PHP 7)compile 出來的 opcode 存在記憶體中,在文件未改變的情況下直接使用 opcode 執行,如此一來就不需要重新再做 Lexical Analysis、Parse 及 Compile。

設定 Opcache

通常會在 php.ini 中對幾個 Opcache 的設定做客製化:

  • opcache.memory_consumption
  • opcahce.interned_strings_buffer
  • opcache.max_accelerated_files
  • opcahce.revalidate_freq = 0
  • opcache.validate_timestamps

opcache.memory_consumption

這個值表示 opcache 在儲存 opcodes 時會使用到多少記憶體,這個大小視自己檔案多寡而定。

可以靠著使用 opcache_get_status() 看到目前使用多少記憶體來儲存 opcodes。opcahce.memory_consuption 的設定要比 opcache_get_status() 所得到的值再大一些才有意義。

單位是 MB。

opcache.interned_strings_buffer

PHP 有個優化性能的特性是 interned strings,它可以將已經出現過的字串儲存於一塊記憶體中,如果該字串重複出現的話就直接將指標指向該記憶體區段,從而減少字串儲存的消耗。

預設情況下,interned string 僅會儲存在各 php-fpm 的 process 中,藉由設定 opcache.interned_strings_buffer 讓各 php-fpm 能夠共享一塊指定大小的 buffer 來改善性能。

單位是 MB。

opcache.max_accelerated_files

這個值代表 Opcache 可以快取多少 PHP 檔案,這個值一定要比專案中所有的檔案數量總合要大,vendor/ 下的也要一併計算進去。

可以用一個很簡單的指令去計算:find ./ -type f -name *.php | wc -l

opcache.revalidate_freq = 0

此值表示多久(單位為秒)檢查一次 PHP 檔案是否有變化,如果檔案發生變化就重新編譯。

通常我會將其設定為 0:在開發環境時當然會希望隨時都使用最新的 PHP Code;在生產環境時這個值並無意義(我們會在下一個設定中讓生產環境永遠不會去檢查 PHP 檔案是否變更)

opcache.validate_timestamps

它決定是否在一段時間後檢查 PHP 檔是否有變化,從而決定是否重新編譯,這個時間由 opcache.revalidate_freq 做決定。

如果這個值設定為 1,它會在每幾秒去確定 PHP 檔案是否發生變化;如果這個值設定為 0,它永遠不會去確定 PHP 檔案是否變化。

通常在開發環境中為 1,在生產環境為 0

關於 opcache.fast_shutdown

很多關於 opcache 的文章都會提到這個值應該設定為 1,然後抱怨官方文件中沒有特別說明。

事實上,這個值在 PHP 7.2 開始就被移除,現在 PHP 會自動處理這些狀況。

關於未來

7.4 的改變

PHP 在 7.4 加入 Preload 的功能,它整合於 Opcache 中。

其原因是 Opcache 雖然降低了編譯時的開銷,但還因為每個 PHP 檔案都是獨立每編譯的,所以在檔案與檔案之間的關係仍然無法被快取起來。

藉由 preload 的機制,我們可以一次載入大量的 PHP 檔案(例如把 composer 的 autoloader 載入),並且提前建立檔案之間的關係。

8.0 的改變

PHP 在 8.0 加入 JIT,它整合於 Opcache 中。

本次的 JIT 會使用 DynASM 作為為基礎去將 op_array 生成 opcodes,根據 RFC 的說明中,開發者原本嘗試過 LLVM 但其效能慢了 100 倍。

V8 引擎得益於其 JIT 的實現才能擁有極高的運行效率,雖然 PHP 的腳步慢了一些,但未來仍是可以期待的。

註:依照目前實現的 JIT,可能會與 Xdebug 等 extension 有所衝突,這問題還需要等待開放新的核心 API 才有解。

後記

Opcache 是 PHP 極重要的一項 Feature,可以讓運行效率大幅提升(而核心開發者也仍為此努力著)。

會建議所有的 PHP 開發者,無論是開發環境或生產環境,都應該要使用 Opcache。


上一篇
Day 14:php.ini 的設定
下一篇
Day 16: 正確取得 IP 位址
系列文
成為 Modern PHPer30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言