PHP 8.5 在性能、调试和运维方面的新特性
2025 年即将结束,这意味着 PHP 的新版本也已经发布了!
在本文中,我们将重点介绍那些你在上述文章中找不到的,关于 PHP 8.5 在性能、调试和运维方面的变化。
其中一些改动甚至是由 Tideways 的员工直接贡献的。
你是不是最好奇 PHP 8.5 是否比旧版本性能更强?可以看看基准测试。
原文链接 PHP 8.5 在性能、调试和运维方面的新特性
性能 (Performance)
针对 === [] 的 OPCode 特殊优化
PHP 编译器可以优化 opcode 以便更高效地运行。在 8.5 版本中,我的同事 Tim 针对 $array === [] 的检查添加了一项优化。在此之前,与 !$array、count($array) === 0 或 empty($array) 相比,这种检查空数组的方式是最慢的。
在 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;
}$i = 1_000_000;
$text = 'Bienvenue chez nous';
while($i--) {foo($text);
}
用于 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
$sh = curl_share_init_persistent([CURL_LOCK_DATA_CONNECT, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_DATA_DNS]);
$ch = curl_init("https://tideways.com");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SHARE, $sh);
curl_exec($ch);foreach (curl_getinfo($ch) as $k => $v) {if (str_ends_with($k, '_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 对象。