2025 年即將結束,這意味着 PHP 的新版本也已經發布了!

在本文中,我們將重點介紹那些你在上述文章中找不到的,關於 PHP 8.5 在性能、調試和運維方面的變化。

其中一些改動甚至是由 Tideways 的員工直接貢獻的。

你是不是最好奇 PHP 8.5 是否比舊版本性能更強?可以看看基準測試。 原文鏈接 PHP 8.5 在性能、調試和運維方面的新特性

性能 (Performance) 針對 === [] 的 OPCode 特殊優化 PHP 編譯器可以優化 opcode 以便更高效地運行。在 8.5 版本中,我的同事 Tim 針對 PHP 8.5 在性能、調試和運維方面的新特性_phparray、count(PHP 8.5 在性能、調試和運維方面的新特性_PHP_02array) 相比,這種檢查空數組的方式是最慢的。

在 PHP 8.5 中,這變成了檢查空數組最快的方式。

那你現在應該把所有代碼都改一遍嗎?不。 但這表明,當你對代碼進行 “微優化” 時,隨着引擎的演進,你可能會在下一個 PHP 版本中失去這種優勢,甚至完全不同的代碼路徑會變得更快。也許最好的做法是:寫你覺得最可讀的代碼,把性能提升押注在未來引擎的改進上。

優化 match (true) PHP 8.5 做的另一個編譯時優化是減少 match (true) 語句生成的 opcode 數量。

對於像下面這樣的人造代碼示例,這帶來了 17% (+/- 6%) 的性能提升:

<?php function foo($text) { $result = match (true) { !!preg_match('/Welcome/', $text), !!preg_match('/Hello/', $text) => 'en', !!preg_match('/Bienvenue/', $text), !!preg_match('/Bonjour/', $text) => 'fr', default => 'other', }; return $result; }

PHP 8.5 在性能、調試和運維方面的新特性_php_03text = 'Bienvenue chez nous'; while(PHP 8.5 在性能、調試和運維方面的新特性_INI_04text); } 用於 DNS、連接和 SSL 握手重用的持久化 cURL 句柄 PHP 的 “無共享(Shared Nothing)” 架構在避免內存泄漏和用户間數據意外泄露方面非常有幫助。但當你在每個請求中反覆執行相同工作時,它也有其弊端。

例如:cURL HTTP 請求中的 DNS 解析、連接建立和 SSL 握手時間。

在 PHP 8.5 中,你可以通過一個新函數 curl_share_init_persistent,在不同 PHP 請求發起的 HTTP 調用之間共享 DNS、連接和 SSL 握手信息,從而顯著減少這些時間

當你在 Web 服務器上多次運行以下 PHP 8.5 代碼時,通過觀察 cURL 計時器,你會發現第二次調用的速度快了很多,以及具體快了多少:

<?php PHP 8.5 在性能、調試和運維方面的新特性_php_05ch = curl_init("https://tideways.com"); curl_setopt(PHP 8.5 在性能、調試和運維方面的新特性_INI_06ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SHARE, PHP 8.5 在性能、調試和運維方面的新特性_INI_07ch);

foreach (curl_getinfo($ch) as $k => PHP 8.5 在性能、調試和運維方面的新特性_INI_08k, '_time')) { echo sprintf("%20s", $k) . "\t" . $v . PHP_EOL; } } 這項變更由 Eric Norris 通過 Add persistent curl share handles 和 Persistent curl share handle improvement RFCs 貢獻。

提升實例化異常和錯誤的性能 通常來説,PHP 應用程序中不應頻繁發生異常,但在某些情況下,它們可能是意外發生的,也可能是完全有意為之。得益於 Niels Dossche 的這個 PR,異常實例化的速度略微變快了。它將一些檢查移動到了 PHP 的調試構建(debug builds)中,並在生產構建中跳過這些檢查。

改進函數性能 以下核心函數在 PHP 8.5 中獲得了性能優化:array_find()、array_filter()、array_reduce()、usort() / uasort()、str_pad()、implode()、pack() 以及 ReflectionProperty::getValue() 及其變體。

運維 (Operations) OPcache 現在是必選擴展 在 PHP 8.5 中,OPcache 現在是一個必選擴展,會自動內置到每個 PHP 二進制文件中。這感謝 Tim、Arnaud 和 Ilija 提交的 RFC。以後不再有 “不帶 OPcache 運行 PHP” 的選項,也不會再發生因意外忘記安裝它而導致的問題 —— 這曾是使用 php 官方 Docker 鏡像時常見的痛點。

其目標是簡化維護,允許以更簡單的方式利用 OPcache 的優化器部分,並將代碼共享 / 移動到引擎中。

不過,你仍然可以通過 php.ini 設置來禁用 OPcache。

OPcache 文件緩存只讀支持 在 PHP 8.5 之前,你無法將 OPcache 的文件緩存與只讀文件系統結合使用。這使得在部署和運行步驟分離的容器環境中(例如在 AWS Lambda 上運行 Bref),無法利用文件緩存。在這些場景中,冷啓動時間是重點優化的對象。

Samuel 的這個 PR 添加了一個新的 INI 選項 opcache.file_cache_read_only。設置該選項後,OPcache 不再廢棄文件,也不會運行任何會修改文件系統的代碼。

該 PR 提到,為了獲得最佳效果,應結合設置 opcache.validate_timestamps=0、opcache.enable_file_override=1 和 opcache.file_cache_consistency_checks=0。

PR 中還包含了 Bref 作者 Matthieu Napoli 的一手經驗,他提到在一個測試應用中,這讓 AWS Lambda 的冷啓動時間減少了 100ms。

新的 max_memory_limit INI 指令 在 PHP 8.5 之前,你可以通過調用 ini_set("memory_limit") 且沒有任何防護措施地將內存限制修改為不健康的高數值。現在,可以通過配置系統級 INI 設置 max_memory_limit 來防止這種情況,它定義了運行時內存限制可以設置的上限。這一變更由 Frederik Pytlick 在此 PR 中貢獻。

新增 PHP_BUILD_PROVIDER 常量 這是另一個對通用應用程序很有幫助的小改動。此前,編譯 PHP 時已經可以使用 PHP_BUILD_PROVIDER 環境變量標誌來提供關於 “誰構建了這個版本” 的信息。這些信息會顯示在 phpinfo() 和 php -v 的輸出中。

通過新的 PHP 常量 PHP_BUILD_PROVIDER,你現在也可以在運行時訪問此信息。

很多人已經在 Homebrew、Debian、Docker 和 Fedora 的上游 PHP 構建中使用了這個功能。

調試 (Debugging) php --ini=diff 通過命令行上的這個新選項,你現在可以查看所有與默認值不同的 INI 變量列表。這對於調試和報告你的 PHP 安裝環境 Bug 非常有幫助。

增加運行時啓用的堆調試能力 得益於 Arnaud 的這個 PR,PHP 8.5 獲得了內存調試能力,而無需使用像 ASAN/MSAN/Valgrind 這樣的專用工具重新編譯 —— 這些工具在生產環境中很難獲得。現在通過設置環境變量 ZEND_MM_DEBUG,你可以啓用不同的內存調試功能。

錯誤回溯 (Error Backtraces) 得益於 Eric Norris 的 RFC,PHP 輸出的致命錯誤(Fatal Errors)現在默認會顯示回溯信息(backtrace)。在此之前,致命錯誤只會顯示錯誤消息。

新函數:get_exception_handler() 和 get_error_handler() 通過 Arnaud 的這個 RFC,PHP 8.5 增加了兩個新函數:get_exception_handler 和 get_error_handler,它們允許你訪問當前的異常或錯誤處理程序的 callable 對象。