PHP Opcode:无需更改代码即可提高应用程序性能

PHP 引擎生成的 opcode 受代码编写方式的影响非常大,不仅仅是完成任务的语句数量。显然,这很重要,我想你也很清楚。

可能不那么明显的是,即使代码的语法也会完全改变生成的 opcode,从而导致机器的 CPU 执行相同代码时产生大量的开销。

在过去的几年里,我开发的 SaaS 产品增长了很多,这使我有机会深入研究优化技术,以便尽可能高效地运行我的工作负载。

我看到的结果令人印象深刻,并且在解锁自由现金流方面帮助了我很多,以便继续我的 SaaS 之旅。

此时,我 SaaS 产品中的 PHP 进程每天在 2vCPU 和 8GB 内存的机器上处理超过 12 亿个数据包。我使用 AWS 自动缩放组来获得更大的灵活性,以应对不可预测的峰值,但很少添加第二台机器(每周一两次)。

让我们深入讨论本文的主题。我想你会发现它非常有趣。

什么是 PHP opcode?

PHP opcode 是 操作码 的缩写,指的是 PHP 引擎在编译你编写的 PHP 源代码后执行的低级指令。在 PHP 中,代码编译发生在运行时:基本上,当你的代码第一次被 PHP 引擎接收时,它会被编译成机器友好的代码,缓存(以便引擎不再编译相同的代码),然后执行。

这是一个过程的简单表示:

PHP 操作码缓存

缓存 PHP 操作码可以让你在执行代码的过程中省去三个步骤:解析原始 PHP 代码、标记化、和编译。

一旦第一次生成了操作码,它就会被存储在内存中,以便在后续请求中重复使用。这减少了 PHP 引擎每次执行相同 PHP 代码时都需要重新编译的需求,从而节省了大量的 CPU 和内存消耗。

PHP 中最常用的操作码缓存是 OPCache,并且从 PHP 5.5 版本开始直到最近版本都是默认包含的。它非常高效且得到广泛支持。

缓存预编译脚本的字节码需要在每次部署后使缓存失效。这是因为如果更改的文件中有字节码版本在缓存中,PHP 会继续运行旧版本的代码,直到你清除操作码缓存,所以新代码将会再次编译生成一个新的缓存条目。

如何调查 PHP 操作码

为了了解不同的语法如何影响脚本的操作码,我们需要一种方法来捕获 PHP 引擎生成的编译代码。

获取操作码有两种方法。

OPCache 原生函数

如果你在自己的机器上启用了 OPCache 扩展,你可以使用其原生函数来获取特定 PHP 文件的操作码:

PHP

 

VLD(Vulcan Logic Disassembler)PHP 扩展

VLD 是一个流行的 PHP 扩展,它可以反汇编编译后的 PHP 代码并输出操作码。它是一个强大的工具,可以帮助理解 PHP 如何解释和执行你的代码。安装后,你可以通过使用 php 命令并带有 -d 选项来运行带有 VLD 的 PHP 脚本:

Shell

 

输出将包含关于编译后操作码的详细信息,包括每个操作、其关联的代码行等。

使用 3v4l(EVAL 的缩写)

3v4l 是一个非常有用的在线工具,允许你查看由你在编辑器中输入的 PHP 代码生成的操作码。它基本上是一个安装了 VLD 的 PHP 服务器,可以抓取 VLD 输出并在浏览器中显示操作码。

由于它是免费的,我们将使用这个在线工具进行下一步的分析。

如何生成高效的 PHP 操作码

3v4l 非常适合理解我们使用的代码语法如何以好的或坏的方式影响生成的 PHP 操作码。让我们开始将下面的代码粘贴到 3v4l 中。保持配置为“所有支持的版本”并点击“eval”。

PHP

 

执行代码后,底部会出现一个选项卡菜单。导航到 VLD 选项卡以查看对应的操作码。

Shell

 

注意,第一个操作是INIT_NS_FCALL_BY_NAME。解释器使用当前文件的命名空间构建函数名称,但在App\Example命名空间中不存在——那么它是如何工作的?

解释器将检查函数是否存在于当前命名空间中。如果不存在,它尝试调用相应的核心函数。

在这里,我们有机会告诉解释器避免这种双重检查,直接执行核心函数。

尝试在strlen之前添加一个反斜杠(\)并点击“eval”:

PHP

 

VLD标签页中,你现在可以看到只有一个语句的opcode。

因为你已经传达了函数的确切位置,所以它不需要考虑任何回退。

如果你不喜欢使用反斜杠,你可以像导入其他类一样从根命名空间导入函数:

PHP

 

利用自动Opcode优化

PHP引擎还有许多内部自动化机制,用于生成预先评估静态表达式的优化opcode。这是自从PHP 7.x版本以来性能大幅提升的最重要原因之一。

了解这些动态真的可以帮助你减少资源消耗和降低成本。自从我进行了这项研究后,我就在代码中开始使用这些技巧。

让我给你一个使用PHP常量的示例。将这个脚本运行到3v4l中:

PHP

 

查看PHP opcode的第一、二行:

FETCH_CONSTANT尝试从当前命名空间获取PHP_OS的值,如果不存在,它将查找全局命名空间。然后,IS_IDENTICAL指令执行IF语句。

现在尝试给常量添加反斜杠:

PHP

 

正如你在opcode中所看到的,引擎不需要尝试获取常量,因为它现在清楚地知道它在哪,而且它是一个静态值,所以它已经在内存中了。

另外,IF语句消失了,因为IS_IDENTICAL语句的另一边是一个静态字符串(‘Linux’),所以IF可以被标记为“true”,而无需在每次执行时解释它。

这就是为什么你有很多力量来影响PHP代码的最终性能的原因。

结论

我希望这是一个有趣的话题。正如我在文章开头提到的,我从这个策略中获得了很多好处,实际上,它们也用在我们的包中。

你可以在这里看到我是如何将这些技巧应用到我们的PHP包中以优化其性能的。

如果你想要了解更多关于建立一个以开发者为导向的公司的挑战,你可以关注我在LinkedIn

Source:
https://dzone.com/articles/php-opcode-improve-application-performance