PHP操作码:无需重构代码即可提高应用程序性能

译文 精选
开发 前端
本文介绍一些语法技巧,可以自动优化PHP代码执行性能,而无需进行复杂的重构。

译者 | 李睿

审校 | 重楼

PHP引擎生成的PHP操作码深受编写代码方式的影响,这不仅仅体现在完成任务所需的语句数量上其次,代码的语法可能完全改变生成的操作码,从而导致服务器的CPU在执行完全相同的代码时会产生大量开销。

在过去的几年,SaaS产品有了巨大的增长,提供了越来越深入优化技术的机会,以尽可能高效地运行工作负载。得出的结果令人印象深刻,可以帮助开发人员释放现金流,以继续SaaS之旅。

在目前SaaS产品的案例中,其PHP进程每天在一台拥有2个vCPU和8GB内存的服务器上处理12亿个以上数据包。并且使用AWS自动扩展组,以便在不可预测的峰值情况下具有更大的灵活性,但很少使用第二台服务器(每周一两次)。

什么是PHP操作码?

PHP操作码指的是PHP引擎在编译完开发人员编写的PHP源代码后执行的低级指令。

在PHP中,代码编译是在运行时runtime发生的:基本上,当PHP引擎首次获取代码时,它会将其编译成这种机器友好的代码,缓存(这样引擎就不会再次编译相同的代码),然后执行。

下图是这个过程的简单表示:

PHP操作码缓存

缓存PHP操作码允许开发人员在执行代码的过程中节省三个步骤:解析原始PHP代码、标记化和编译。

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

PHP中最常用的操作码缓存是OPCache,从PHP 5.5到最近的版本默认包含了OPCache。其效率高,并得到了广泛支持。

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

如何调查PHP操作码

为了理解不同的语法如何影响脚本的操作码,需要获取PHP引擎生成的编译代码的方法。

有两种方法可以获得操作码。

OPCache原生函数

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

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(Vulcan逻辑反汇编器)PHP扩展

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

Shell 
 php -d vld.active=1 -d vld.execute=0 yourscript.php

输出将包括有关编译后的操作码的详细信息,包括每个操作及其相关的代码行等等。

使用3v4l(EVAL的缩写)

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

由于它是免费的,将在以下的分析中使用这个在线工具。

如何生成高效的PHP操作码

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

PHP 
 <?php

 namespace App;

 strlen('ciao');

在执行代码后,底部将出现一个选项卡菜单。导航到VLD选项卡以可视化相应的操作码。

Shell 
 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。解释器使用当前文件的名称空间构造函数的名称,但在 App\Example 命名空间中并不存在这个函数——那么它是如何工作的呢?

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

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

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

PHP 
 <?php

 namespace App;

 \strlen('ciao');

在VLD选项卡中,现在只需一条语句即可看到操作码。

line #* E I O op fetch ext return operands
 ------------------------------------------------------------------------------------- 5 0 E > > RETURN 1

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

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

PHP 
 <?php

 namespace App;

 use function strlen;

 strlen('ciao');

利用自动操作码优化

PHP引擎还有很多内部自动化功能,可以提前生成优化的操作码,对静态表达式进行求值。这是PHP自7.x版本以来性能显著提高的最重要原因之一。

了解这些动态可以真正减少资源消耗并降低成本。在进行这项研究之后,已经开始在整个代码中使用这些技巧。

以下展示一个使用PHP常量的示例。在3v4l中运行这个脚本:

PHP 
 <?php

 namespace App;

 if (PHP_OS === 'Linux') {
 echo "Linux";
 }

查看PHP操作码的前两行内容:

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尝试从当前名称空间获取PHP_OS的值,它将查找全局名称空间,因为它在此处不存在。然后,IS_IDENTICAL指令执行IF语句。

现在尝试将反斜杠添加到常量中:

PHP 
 <?php

 namespace App;

 if (\PHP_OS === 'Linux') {
 echo "Linux";
 }

正如在操作码中看到的那样,引擎不需要尝试获取常量,因为其位置现已明确,并且作为一个静态值,它已经被存储在内存中。

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

这凸显了开发人员在PHP代码的最终性能上可以产生的重要影响。

结论

希望这是一个有趣的话题。正如在文章开头提到的那样,开发人员通过使用这种策略获得了很多好处,事实上,它们也在软件包中使用。

可以在这里看到一个示例,说明如何在PHP包中使用这些技巧来优化其性能。

原文标题:PHP Opcode: Improve Application Performance Without Changing Your Code,作者:Valerio Barbera


责任编辑:华轩 来源: 51CTO
相关推荐

2022-07-04 17:32:12

DevOpsAIOps

2009-07-29 11:33:14

ASP.NET技巧ASP.NET应用程序

2010-02-04 09:41:03

Android应用程序

2017-06-13 17:23:53

存储缓存数据

2011-07-07 09:03:12

ASP.NET

2019-10-17 10:10:23

优化Web前端

2009-07-01 18:24:59

JSP应用程序JMeter

2020-04-10 09:05:40

iOS功能代码

2011-08-08 13:35:50

Web应用WANWeb应用程序

2021-08-18 09:37:51

数据库移动应用程序

2011-09-20 10:41:45

Web

2010-11-15 16:20:33

Oracle系统优化

2014-12-16 09:35:13

DevOps

2011-03-14 09:35:26

LAMP调优方法

2020-12-03 08:00:00

SQL数据库MySQL

2011-01-19 11:10:50

程序交付优化应用程序性能管理监控

2009-12-23 10:29:01

WPF应用程序

2012-11-28 11:09:28

IBMdW

2010-07-28 09:25:41

jQueryJavaScript性

2009-01-08 19:11:39

服务器应用程序SQL Server
点赞
收藏

51CTO技术栈公众号