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
这是因为你传达了函数的确切位置,所以它不需要考虑任何后备。
If don't like to use the the backslash, you can import the function like any other class from the root namespace:
<?php namespace App; use function strlen; strlen('ciao');
There are also a lot of internal automatisms of the PHP engine to generate an optimized opcode evaluating static expressions in advance. This was one of the most important reasons of the great performance improvement of PHP since the version 7.x
Being aware of these dynamics can really help you reduce resource consumption and cut costs. Once I made this research, I started using these tricks throughout the code.
Let me show you an example using PHP constants. Run this script into 3v4l:
<?php namespace App; if (PHP_OS === 'Linux') { echo "Linux"; }
Take a look at the first two lines of the PHP opcode:
line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 5 0 E > FETCH_CONSTANT ~0 'App%5CPHP_OS' 1 IS_IDENTICAL ~0, 'Linux' 2 > JMPZ ~1, ->4 6 3 > ECHO 'Linux' 7 4 > > RETURN 1
FETCH_CONSTANT try to get the value of PHP_OS from the current namespace and it will look into the global namespace as it doesn’t exist here. Then the IS_IDENTICAL instruction executes the IF statement.
Now try adding the backslash to constant:
<?php namespace App; if (\PHP_OS === 'Linux') { echo "Linux"; }
As you can see in the opcode the engine doesn't need to try to fetch the constant because now it's clear where it is, and since it's a static value it already has it in memory.
Also the IF statement disappeared because the other side of the IS_IDENTITCAL statement is a static string ('Linux') so the IF can be marked as "true" without the overhead of interpreting it on every execution.
This is why you have a lot of power to influence the ultimate performance of your PHP code.
I hope it was an interesting topic, as I mentioned at the beginning of the article I'm getting a lot of benefits from using this tactic and in fact they are also used in our packages.
You can see here an example of how I used this tips in our PHP package to optimize its performance: https://github.com/inspector-apm/inspector-php/blob/master/src/Inspector.php#L302
For more technical articles you can follow me on Linkedin or X.
Inspector is a Code Execution Monitoring tool specifically designed for software developers. You don't need to install anything at the server level, just install the Laravel or Symfony package and you are ready to go.
If you are looking for HTTP monitoring, database query insights, and the ability to forward alerts and notifications into your preferred messaging environment, try Inspector for free. Register your account.
Or learn more on the website: https://inspector.dev
以上是PHP 操作码 – 无需更改代码即可提高应用程序性能的详细内容。更多信息请关注PHP中文网其他相关文章!