PHP 引擎生成的 PHP 操作码很大程度上受您编写代码的方式影响。不仅仅在于完成任务的语句数量。显然这很重要,我认为这对你来说是显而易见的。
不太明显的是,即使代码的语法也可以完全改变生成的操作码,导致机器的 CPU 执行完全相同的代码产生大量开销。
在过去的几年里,我的 SaaS 产品有了很大的发展,它让我有机会越来越深入地研究优化技术,以尽可能高效地运行我的工作负载。
我看到的结果令人印象深刻,并且在释放自由现金流以继续开发我的 SaaS 之旅方面帮助了我很多。
此时,我的 SaaS 产品内的 PHP 进程每天在具有 2vCPU 和 8GB 内存的机器上处理超过 12 亿个(带 B)数据包。 我使用 AWS 自动缩放组在出现不可预测的峰值时拥有更大的灵活性,但它很少添加第二台机器(每周一/两次)。
想要了解更多技术文章,您可以在 Linkedin 或 X 上关注我。
最近我还写了关于 Inspector 服务器到 ARM 实例的迁移:https://inspector.dev/inspector-adoption-of-graviton-arm-instances-and-what-results-weve-seen/
下面进入文章主题吧。我想你会发现它很有趣。
PHP opcode 代表操作码,是指您编写的 PHP 源代码编译后,由 PHP 引擎执行的低级指令。
在 PHP 中,代码编译发生在运行时,基本上,您的代码第一次被 PHP 引擎获取时,它会被编译成机器友好的代码并缓存,因此引擎不会再次编译相同的代码,并且然后被处决。
这是该过程的简单表示:
缓存 PHP 操作码可以让您在执行代码的过程中节省三个步骤:解析原始 PHP 代码、标记化和编译。
第一次生成操作码后,它会存储在内存中,以便在后续请求中重用。这减少了 PHP 引擎每次执行时重新编译相同 PHP 代码的需要,节省了大量的 CPU 和内存消耗。
PHP 中最常用的操作码缓存是 OPCache,从 PHP 5.5 到最新版本默认包含它。它效率高且得到广泛支持。
缓存预编译脚本字节码需要在每次部署后使缓存失效。因为如果更改的文件在缓存中具有字节码版本,PHP 将继续运行旧版本的代码。直到您清除操作码缓存,以便再次编译新代码并生成新的缓存项。
要了解不同的语法如何影响脚本的操作码,我们需要一种方法来获取 PHP 引擎生成的编译代码。
有两种获取操作码的方法。
如果您的计算机上启用了 OPCache 扩展,您可以使用其本机函数来获取特定 php 文件的操作码:
// Force compilation of a script opcache_compile_file(__DIR__.'/yourscript.php'); // Get OPcache status $status = opcache_get_status(); // Inspect the script's entry in the cache print_r($status['scripts'][__DIR__.'/yourscript.php']);
VLD 是一个流行的 PHP 扩展,它可以反汇编已编译的 PHP 代码并输出操作码。它是了解 PHP 如何解释和执行代码的强大工具。安装后,您可以使用带有 -d 选项的 php 命令来运行启用了 VLD 的 PHP 脚本:
php -d vld.active=1 -d vld.execute=0 yourscript.php
输出将包含有关已编译操作码的详细信息,包括每个操作、其关联的代码行等等。
3v4l 是一个非常有用的在线工具,它允许您查看在编辑器中输入的 PHP 代码生成的操作码。它基本上是一个安装了 VLD 的 PHP 服务器,因此它可以抓取 VLD 输出并向您显示浏览器中的操作码。
由于它是免费分发的,我们将使用这个在线工具进行下一步分析。
3v4l 非常适合理解我们使用的代码语法如何以好或坏的方式影响生成的 PHP 操作码。让我们开始将下面的代码粘贴到 3v4l 中。保留配置“所有支持的版本”,然后单击“评估”。
<?php namespace App; strlen('ciao');
执行代码后,底部会出现一个选项卡菜单。导航到 VLD 选项卡以可视化相应的操作码。
line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 5 0 E > INIT_NS_FCALL_BY_NAME 'App%5CSpace%5Cstrlen' 1 SEND_VAL_EX 'ciao' 2 DO_FCALL 0 3 > RETURN 1
请注意,第一个操作是 INIT_NS_FCALL_BY_NAME。解释器使用当前文件的命名空间构造函数的名称。但它并不存在于 AppExample 命名空间中,那么它是如何工作的呢?
解释器将检查该函数是否存在于当前命名空间中。如果没有,它会尝试调用相应的核心函数。
这里我们有机会告诉解释器避免这种双重检查,直接执行核心函数。
尝试在strlen前加一个反斜杠(),然后点击“eval”:
<?php namespace App; \strlen('ciao');
在 VLD 选项卡中,您现在只需一条语句即可看到操作码。
line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 5 0 E > > RETURN 1
这是因为你传达了函数的确切位置,所以它不需要考虑任何后备。
如果不喜欢使用反斜杠,您可以像从根命名空间导入任何其他类一样导入该函数:
// Force compilation of a script opcache_compile_file(__DIR__.'/yourscript.php'); // Get OPcache status $status = opcache_get_status(); // Inspect the script's entry in the cache print_r($status['scripts'][__DIR__.'/yourscript.php']);
PHP 引擎还有很多内部自动功能来提前生成评估静态表达式的优化操作码。这是 PHP 自 7.x 版本以来性能大幅提升的最重要原因之一
了解这些动态确实可以帮助您减少资源消耗并降低成本。一旦我进行了这项研究,我就开始在整个代码中使用这些技巧。
让我向您展示一个使用 PHP 常量的示例。在 3v4l 中运行此脚本:
php -d vld.active=1 -d vld.execute=0 yourscript.php
看一下 PHP 操作码的前两行:
<?php namespace App; strlen('ciao');
FETCH_CONSTANT 尝试从当前命名空间获取 PHP_OS 的值,它将查找全局命名空间,因为这里不存在该值。然后 IS_IDENTICAL 指令执行 IF 语句。
现在尝试将反斜杠添加到常量:
line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 5 0 E > INIT_NS_FCALL_BY_NAME 'App%5CSpace%5Cstrlen' 1 SEND_VAL_EX 'ciao' 2 DO_FCALL 0 3 > RETURN 1
正如您在操作码中看到的,引擎不需要尝试获取常量,因为现在它在哪里很清楚,并且由于它是一个静态值,因此它已经在内存中了。
IF 语句也消失了,因为 IS_IDENTITCAL 语句的另一端是静态字符串('Linux'),因此 IF 可以标记为“true”,而无需在每次执行时解释它的开销。
这就是为什么您有很大的权力来影响 PHP 代码的最终性能。
我希望这是一个有趣的话题,正如我在文章开头提到的那样,我从使用这种策略中获得了很多好处,事实上它们也在我们的包中使用。
您可以在这里看到我如何在 PHP 包中使用这些技巧来优化其性能的示例:https://github.com/inspector-apm/inspector-php/blob/master/src/Inspector.php# L302
想要了解更多技术文章,您可以在 Linkedin 或 X 上关注我。
Inspector是一款专为软件开发人员设计的代码执行监控工具。您无需在服务器级别安装任何内容,只需安装 Laravel 或 Symfony 软件包即可开始使用。
如果您正在寻找 HTTP 监控、数据库查询见解以及将警报和通知转发到您首选消息传递环境的能力,请免费尝试 Inspector。注册您的帐户。
或在网站上了解更多信息:https://inspector.dev
以上是PHP 操作码 – 无需更改代码即可提高应用程序性能的详细内容。更多信息请关注PHP中文网其他相关文章!