从PHP5.0到PHP7.1的性能全评测
本文是最初是来自国外的这篇:PHP Performance Evolution 2016, 感谢高可用架构公众号翻译成了中文版, 此处是转载的高可用架构翻译后的文章从PHP 5到PHP 7性能全评测(含未发布的JIT版PHP 8对比), 稍微调整了格式而成。
导读:PHP 是 Web 开发最常用的语言,每个大版本的更新都带来不少新特性和性能提升。特别是 PHP 7.0 的发布,带来 PHP 性能飞跃。本文作者对各个 PHP 版本进行了 CPU 性能基准测试,并且带来了PHP下个大版本的消息。本文中文版由高可用架构志愿者翻译。
自 1994 年 Rasmus Lerdorf 创建 PHP 以来, PHP 语言经历了许多改进,其中性能是开发人员在评估新版本时考虑的主要标准之一。
阅读这篇文章,可以了解从 PHP 5 到 7(包括 7.1)的性能提升,同时也将了解到即将加入到 PHP 8 的试验性的 JIT 分支版本的性能。
简介
本文将根据时间作出更新,增加更多信息和基准测试结果,包括尚未发布的新版本,以便更好地了解多年来 PHP 性能演变。如果您有更正或建议改进,请在文后留言。
自 1994 年 Rasmus Lerdorf 创建 PHP 以来, PHP 语言经历了激烈的演进。虽然第一版是一个简单的一人开发的 CGI 程序,Rasmus Lerdorf、Andi Gutmans 和 Zeev Suraski 加入了该语言的第三个版本的开发,并根本性重新设计。从那之后, PHP 开发组也创建并发展起来。
随着项目的发展,由于 PHP 3 天然的可扩展性, PHP 在核心和附加扩展开发的功能得到了蓬勃发展,如网络通信,解析,缓存和数据库支持。
语言本身也在发展,带来了一系列的改进。这包括支持面向对象的结构,例如类,接口, traits,闭包等。
对于许多开发人员来说,仅有新功能是不够的。随着语言越来越受欢迎, PHP 社区对于提供更好性能,可扩展性和更少内存使用的需求越来越强烈。
PHP 开发团队近 20 年来一直致力于解决这些需求,虽然 PHP 3 的引入大大提高了性能,但直到 Andi Gutmans 和 Zeev Suraski 引入 Zend Engine 并发布 PHP 4, PHP 的性能才开始变得正式起来。
2000 年推出的新的内存编译器和执行器模型大大提高了 PHP 的性能(提高了 5 倍甚至 10 倍),并首次被正式的 Web 应用程序和站点所使用。我们可以说,今天 PHP 的成果远远超出了任何人在 PHP 项目诞生时的期望。
PHP 的蓬勃发展增加了改善性能的欲望。幸运的是, Zend Engine 中设计的模型为持续优化性能提供了良好的基础。
虽然 PHP 5.0 没有带来实质性的性能提升,并且在某些情况下甚至比 PHP4 更慢,一个由 Dmitry Stogov 领导的团队在社区的大力帮助下已经在后续版本中不断优化语言,在 PHP 5.6 发布的时候,在大多数情况下,性能提升在 1.5x 和 3x 之间。
2015 年 12 月, PHP 7.0 取得了重大突破。 2016 年 12 月,7.1 版本也带来了一系列增强功能。
PHP 8 性能展望
这是一个前途光明的版本,目前正在开发当中,由 Zend 的 Dmitry Stogov 主导。虽然它是基于 PHP 7.1 版本基础,但实际版本号尚未定义,所以本文称这个版本为“试验 JIT”分支下。
关键功能 JIT(Just-In-Time)编译,是一种将代码转换为另一种字节码(比如运行它的机器 CPU 的本地代码)的技术。 JIT 可以使程序运行更快。
本文涵盖了几个基准测试的结果,从 PHP 5 的第一个版本到 PHP 的试验性 JIT 分支版本,PHP 5 之前的版本性能本文不作介绍。
在写这篇文章的时候,我们很难确定 PHP 8 之前是否会有另一个主要版本,比如 PHP 7.2。但是可以假设在 PHP 8 发布时,它已经包括当前试验版 JIT 分支的强大功能。
PHP 性能评估
本文只运行纯 CPU 任务脚本的基准测试(不需要I / O操作的任务例如访问文件,网络或数据库连接)。
使用的基准测试脚本如下所示:
- bench.php 可在PHP源代码的 php-src/Zend 目录
- micro_bench.php 也可以在 PHP 源代码发布的 php-src/Zend 目录中找到
- mandelbrot.php https://gist.githubusercontent.com/dstogov/12323ad13d3240aee8f1/raw/37fed3beb7e666b70e199bcf361af541b3c30d2d/b.php
基准脚本仅使用每个PHP主要版本的最新小版本运行。因此,测试的版本如下:
- 5.0.5
- 5.1.6
- 5.2.17
- 5.3.29
- 5.4.45
- 5.5.38
- 5.6.28
- 7.0.13
- 7.1.0
- PHP-JIT(JIT实验分支)
当然,我想确定,我们在相同的基准上运行所有小版本,例如在 5.3.0 到 5.3.29 之间。结果是有说服力的:性能方面的主要增强不是由小版本带来的,而是主要版本号的变化,例如从 PHP 5.4 到 PHP 5.5,或从PHP 5.6 到 PHP 7。
小版本没有显示任何明显的性能改进。这意味着相同的脚本应该以相同的速度运行,无论您使用 PHP 5.4.0 还是 PHP 5.4.45。
您可以查看基准进程部分,详细说明主机系统的设置,各个基准的运行方式以及如何解释时序结果。
纯 CPU 基准测试结果
这部分给出了每个 PHP 版本的基准测试结果。
每个基准列显示 3 个值:
- 时间: 执行时间,以秒和毫秒为单位
- %rel, gain:相对于以前的版本收益的执行时间。 在下面的表格中,例如,%rel。 bench.php 和版本 5.3.29 的收益是 31.89%,意味着该脚本比 5.2.17 版本运行快 31.89%。
- %abs, gain:与 PHP 5.0 相比脚本运行的收益。 如果你看看bench.php 和试验性的 JIT 分支的这个列的交集,你会注意到,对于这个特定的测试基准,PHP 8 比 PHP 5.0 快 41 倍以上。
纯CPU基准测试的结果如下所示:
CPU基准测试
(1)测试不能在 5.3 之前的版本上运行,因为它使用了尚未实现的对象功能。
(2)此列中的结果有点偏颇,因为基准需要至少 PHP 5.3 运行。把它们当成纯粹说明,因为他们不能与 PHP 5.0 性能进行比较。
(3)这是一个 mandelbrot.php 脚本的修改版本,它运行得太快,在 7.1.0 和试验 JIT 分支无法准确的统计时间,我们在脚本中运行计算 100 次而不是 1 次。
当然,这些都是纯 CPU 的基准测试。它们不涵盖 PHP 性能的所有方面,它们可能不代表真实情况。但是结果足够显著,足以说明几个方面的问题:
- PHP 5.1 将 PHP 5.0的 性能提高了一倍多
- 5.2 和 5.3 带来了他们自己的一些性能增强,但他们没有像5.1版本那样引人注目。
- 5.4 版本是一个大的性能改进。(这里有我曾经分享过的PHP5.4的性能优化的演讲PPTPHP-5.4 Performance)
- opcache 扩展插件与 5.5 和 5.6 版捆绑在一起。当相同的脚本在 Web 服务器连续运行时,由于更快的代码加载会带来性能增强。但是,opcache 不会真正显示其在CLI模式下执行脚本的优势。
- PHP 7.0 是性能方面的一个重大突破。 Zend Engine 已经完全重新设计,我们可以在这里看到这项工作的结果。(这里有我曾经分享过的PHP7的性能优化的演讲的PPTThe secret of PHP7’s Performance )
- PHP 7.1 在 opcache 扩展中引入了 opcode 优化。这再次解释了上述表格中当与 7.0 相比时,性能的增益。(这里有我曾经分享过的PHP7.1的性能优化的演讲PPTPHP 7.1’s New Features and Performance
- 试验 JIT 分支是另一个重大突破,JIT 可以对现有代码提供很大的性能改进,但在某些情况下,你可能会注意到速度提高只有几个百分点,在最坏的情况下,它甚至可能会变慢,因为编译不会生成更快的代码。请记住,此功能目前正在开发中。
本节介绍了 3 个纯 CPU 基准测试脚本的结果。在运行通常执行的以数据库或文件访问典型场景的 PHP 应用程序时,它不会给出同样的数字,但我认为他们能够代表您对代码的某些部分期望的性能改进。
PHP 每个版本的性能提升
PHP 5 相比 PHP 4 带来了明显的改进。 Zend Engine 是 PHP 解释器的核心,它已经完全重新设计( Zend Engine 2),为将来的增强功能奠定了基础。本文不多介绍 PHP 4 和 PHP 5 之间的差异,只简要概述的 PHP 5.0 之后发生了什么。
以下部分列出了在后续 PHP 版本中的改进。请注意,这里仅列出影响 PHP 核心的修改。有关更完整的描述,请查看 PHP 5 和 PHP 7 的change log。
PHP 5.1
- Compiled variables
- Specialized executor
- Real-path cache
- Faster switch() statement handling
- Faster array functions
- Faster variable fetches
- Faster magic method invocations
PHP 5.2
- New memory manager
- Optimized array/HashTable copying
- Optimized require_once() and include_once() statements
- Small optimization on specific internal functions
- Improved compilation of HEREDOCS and compilation of interpolated strings
PHP 5.3
- Segmented VM stack
- Stackless VM
- Compile-time constants substitution
- Lazy symbol table initialization
- Real-path cache improvement
- Improved PHP runtime speed and memory usage
- Faster language parsing
- Improved PHP binary size and code startup
PHP 5.4
- Delayed HashTable allocation
- Constant tables
- Run-Time binding caches
- Interned Strings
- Improved the output layer
- Improved ternary operator performance when using arrays
PHP 5.5
- Improved VM calling convention
- OPcache integration
- Other misc. optimizations to the Zend Engine
PHP 5.6
- Optimized empty string handling, minimizing the need to allocate new empty values
PHP 7.0
下面大部分列出的改进都与 Zend Engine 相关:
- Core data structures re-factoring
- Better VM calling convention
- New parameters parsing API
- Yet another new memory manager
- Many improvements in VM executor
- Significantly reduced memory usage
- Improved __call() and __callStatic() functions
- Improved string concatenation
- Improved character searching in strings
PHP 7.1
- New SSA based optimization framework (embedded into opcache)
- Global optimization of PHP bytecode based on type inference
- Highly specialized VM opcode handlers
PHP 8 / 下一代试验性 JIT 分支版
- Just-In-Time compiling
性能如何衡量
基准化比单纯运行 Unix 时间命令来测量脚本的执行有所区别。 这就是为什么我经历了以下步骤:
配置系统
首先我设置了一个具有以下特性的专用系统:
- 一个带有1个2.4GHz虚拟内核,2GB RAM和两个SSD驱动器的VPS,一个用于存储操作系统数据,另一个用于存储各种PHPyuan dai ma,二进制文件和报告输出
- Debian Wheezy操作系统,版本3.2.82-1
- Gnu C编译器版本4.9.2-10(Debian Jessie发行版)
- 虽然系统捆绑了Gnu C编译器版本4.7.2,但需要升级到更新的版本。 试验性 JIT 分支必须用Gnu C> = 4.8编译。
编译源代码
在构建完整发行版之前,使用以下选项运行配置脚本:
- --prefix=/usr/local/php
- --disable-debug
- --disable-phpdbg
- --enable-mysqlnd
- --enable-bcmath
- --with-bz2=/usr
- --enable-calendar
- --with-curl
- --enable-exif
- --enable-fpm
- --with-freetype-dir
- --enable-ftp
- --with-gd
- --enable-gd-jis-conv
- --enable-gd-native-ttf
- --with-gettext=/usr
- --with-gmp
- --with-iconv
- --enable-intl
- --with-jpeg-dir
- --enable-mbstring
- --with-mcrypt
- --with-openssl
- --enable-pcntl
- --with-pdo-mysql=mysqlnd
- --with-png-dir
- --with-recode=/usr
- --enable-shmop
- --enable-soap
- --enable-sockets
- --enable-sysvmsg
- --enable-sysvsem
- --enable-sysvshm
- --enable-wddx
- --with-xmlrpc
- --with-xsl
- --with-zlib=/usr
- --enable-zip
- --with-mysqli=mysqlnd
注意,在编译旧版时,上面的一些选项需要被禁用或被其他替代,并且并不是所有的扩展都可用或可以被编译。
运行基准测试
每个基准测试都使用 PHP CLI 专用脚本运行,该脚本遵循以下步骤:
使用 microtime()函数从内部获取脚本执行时间。 在此修改后,基准脚本将如下所示:
- <?php
- $__start__ = microtime( true );
- /***
- original benchmark script code here
- ***/
- fprintf( STDERR, microtime( true ) - $__start__);
- ?>
执行 2 次运行,以确保 PHP 可执行文件和基准测试脚本内容都在操作系统缓存中
运行脚本 5 次,并提取最小,最大和平均运行时间,如脚本报告。 本文仅显示平均运行时间,称之为“脚本运行时间”。
php.ini 文件如下所示:
- engine = On
- short_open_tag = Off
- realpath_cache_size = 2M
- max_execution_time = 86400
- memory_limit = 1024M
- error_reporting = 0
- display_errors = 0
- display_startup_errors = 0
- log_errors = 0
- default_charset = "UTF-8"
- [opcache]
- zend_extension=opcache.so
- opcache.enable=1
- opcache.enable_cli=1
- opcache.optimization_level=-1
- opcache.fast_shutdown=1
- opcache.validate_timestamps=1
- opcache.revalidate_freq=60
- opcache.use_cwd=1
- opcache.max_accelerated_files=100000
- opcache.max_wasted_percentage=5
- opcache.memory_consumption=128
- opcache.consistency_checks=0
- opcache.huge_code_pages=1
- // PHP 8/Next only
- opcache.jit=35
- opcache.jit_buffer_size=32M
分析运行结果
使用 Unix time 命令来计时,输出如下所示:
- $ time php bench.php
- real: 0m1.96s
- user: 0m1.912s
- sys: 0m0.044s
第一个值,real : 是命令开始到终止之间的时间(当你回到 shell 提示符)。
第二个值,user :说明在用户模式中花费的时间(在我们的例子中,这是在 php 可执行文件中花费的时间)。
最后一个值 sys :说明在操作系统(内核)调用中花费的时间。这个值应该是最小的,但是如果你的代码访问缓慢的设备结果会比较大。重负载的操作系统也可能影响此处报告的值。
在空闲系统上通常,数量(user + sys)应该非常接近 real。这是在上面的例子中的情况:user + sys = 1.956s,real 是 1.960s。 0.004s 的差异不属于当前进程:它仅仅意味着操作系统执行任务所花费的额外时间,例如调度。
同一个脚本在一个负载很重的系统上执行,并行编译 3 个不同的 PHP 版本:
- $ time php bench.php
- real: 0m7.812s
- user: 0m2.02s
- sys: 0m0.101s
在这里我清楚地看到,系统本身的重负载对使用的时间(也许在系统时间)有重大影响。
这就是为什么我在这个基准中保留一个额外的值,操作系统开销,这是调用的时间和(用户+系统)时间之间的差。
在纯 CPU 基准测试活动期间,我确保在 99% 以上的时间,这个值严格小于 100 毫秒,即使运行需要几十秒钟完成的脚本。
特别鸣谢
特别鸣谢 Dmitry Stogov 和所有 PHP 核心开发者们。
本文是和 Dmitry Stogov 合作完成的 , 他帮助审阅了文章内容 , 来保证这个文章的正确性。
Dmitry Stogov 曾经是 Truck MMCache 的开发者,在 PHP4 时代就可以用作共享内存中缓存 PHP Opcode,从那时候起,Dmitry Stogov 就加入了 Zend,一直到现在。
Dmitry 是 PHP NG 的主要开发者 , 也就是我们后来知道的 PHP7, 和 Dmitry 一起合作的有 Xinchen Hui(就是我 :)),Nikita Popov,正是他们在一起开发了 PHP7 以及后来的版本包括 PHP JIT。
在 PHP7 之前 , PHP5 时代的 Andi Gumans, Zeev Suraski 以及 Stas Malishev 等也做了很多的工作来提升 PHP5 的性能,限于篇幅,本文就不详细介绍。
结论
本文的目的是给你一个不同版本PHP性能的概述,从 5.0 开始,到当前正在开发的最新版本,使用一组已知的基准脚本。
它还为您提供了由每个连续 PHP 版本解决的性能改进方面的列表。
本文将随着新的 PHP 版本的公布而更新,并且将来会添加新的基准测试结果。 我也希望添加一些真实世界的 PHP 应用程序,如 WordPress 的基准测试结果。
如果您有任何问题或发现不准确,请随时发表评论。 同时,与其他对 PHP 性能感兴趣的开发人员分享这篇文章。