PHP7和HHVM的性能比较,那个性能更好

【导读】徐汉彬曾在阿里巴巴和腾讯从事4年多的技术研发工作,负责过日请求量过亿的web系统升级与重构,目前在小满科技创业,从事saas服务技术建设。最近,php7和hhvm的性能之争成为了一个讨论热点,它们都在提升php执行性能方面取得了突破性的进展。这篇文章,参考了两个社区的技术新进展,为大家科普和介绍它们的性能之争。
php语言的排名变化根据“tiobe编程语言排行榜”(榜单虽然统计方式有局限,但是仍然不失为一个比较好的参考),2010年php最高曾经在世界编程语言中排名第三。可见,php语言在pc互联网时代的web领域可谓叱咤风云,擎天一柱。
在php程序员中,曾经流传着一个段子:
某女:你能让这个论坛的人都吵起来,我就跟你吃饭。
php程序员:php是世界上最好的语言!
某论坛炸锅了,各种吵架……
某女:服了你了,我们走吧!
php程序员:今天不行,我一定要说服他们,php必须是最好的语言。
好了,我们言归正传,语言本身无分好坏,只是在各自使用的场景中解决不同的问题。互联网的时代车轮是很快的,随着移动互联网的到来,在短短四年多的时间里,移动端技术发展横扫全球。与此同时,各种语言群雄并起,而昔日辉煌的php从原来的编程语言的榜单看,下降到第六位(2014年12月榜单)。于是,唱衰php的声音此起彼伏。
但是,鸟哥(惠新宸,php语言开发者之一)在2014年的qcon分享中有一个数据,全球排名前100万的网站中,81.3%使用的web服务端脚本语言是php,2013年同期是78.3%。也就是说,php的在web服务方面并没有减少,只是在移动互联网浪潮中,增加了很多的其他语言技术的应用,进而被稀释了。
最近关于php7和hhvm的性能对比,成为了一个热点的争议话题,大家都在讨论和关注哪一个才是php性能提升的未来。
hhvm(hiphop virtual machine)的起源hhvm是一个开源的php虚拟机,使用jit的编译方式以及其他技术,让php代码的执行性能大幅提升。据传,可以将当前版本的原生php代码提升5-10倍的执行性能。
hhvm起源于facebook公司,facebook早起的很多代码是使用php来开发的,但是,随着业务的快速发展,php执行效率成为越来越明显的问题。为了优化执行效率,facebook在2008年就开始使用hiphop,这是一种php执行引擎,最初是为了将fackbook的大量php代码转成 c++,以提高性能和节约资源。使用hiphop的php代码在性能上有数倍的提升。后来,facebook将hiphop平台开源,逐渐发展为现在的hhvm。
1. php为什么慢?
php的慢是相对于c/c++级别的语言来说,事实上,php语言最初的设计,就不是用来解决计算密集型的应用场景。我们可以这样粗略理解为,php为了提升开发效率,而牺牲了执行效率。
我们知道php一个很大的特点,就是弱类型特性,也就是说,我可以随意定义一个变量,然后给它随意赋值为各种类型的数据。以一个int整型数字为例子,在c语言中:
int num = 200;//通常是4字节
但是,如果是php定义了一个同样的变量,实际对应的存储结构则是:
这个结构体将会占据远比c变量多得多的内存,php中定义方式如下:
$a = 200;//这变量将实际占用对比c变量很多倍的存储空间。
其实对php来说,无论存储什么类型的数据,都是用上述“通杀”的结构体实现。为了兼容php程序员的变量类型“乱入”,php做到了对开发者的友好,但是对执行引擎很残酷。单个变量内存消耗可能还不明显,一旦用到php的数组等,则复杂度指数上升(数组的实现是hashtable)。然后,zend引擎执行时,将这些php代码编译为opcode(php的中间字节码,格式有点类似于汇编),由zend引擎逐行解释执行。
无论是字符串的连接操作,还是数组的简单修改等,几乎都是“php程序员一句话,zend引擎跑断腿”的节奏。因此,同样的操作,对比c来说,php消耗了更多的cpu和内存等系统资源。除此之外,还有内存自动回收、变量类型判断等等,都会增加系统资源的消耗。
例如,我用纯php实现的快速排序函数和原生sort函数,排序10000个整型数字,来做一个耗时对比,结果如下:
原生的sort耗时3.44 ms,而我们自己实现的php函数sort则是68.79 ms。我们发现,两者执行效率差距巨大。我的测试方式,是计算函数执行前后的时间间隔,而不是整个php脚本从启动到结束的时间。php脚本启动和关闭过程,本身有着一系列的初始化和清理工作,也会占据不少的耗时。
通常情况下,php执行效率的排行是:
最快的是php语言结构(isset、echo等),php语言的一部分(它们根本不是函数)。然后比较快的就是php的原生和拓展函数。php拓展,基于zend api之上,用c实现的功能,执行效率和c++/java是属于同一个数量级的。真正慢的就是,我们通过php自己写的代码和函数。例如,假如我们使用的比较重的纯php实现的框架,因为框架本身的模块很多,所以,会明显拖累语言层面的执行效率,同时占据更多的内存。(国内的yaf框架,以拓展的方式实现,因此执行效率远快于纯php写的框架)
在一般情况下,我们并不推荐用过php实现逻辑复杂计算类型的功能,尤其是web系统流量比较大的场景下。因此,php程序员应该对php的各种原生函数和各类拓展有一个比较广泛的了解,在具体的功能实现场景中,寻求更原生的解决方案(原生接口或者拓展),而不是自己写一堆复杂的php代码来实现这类型功能。
如果有足够的php拓展开发实力,将这类型业务功能重写为一个php拓展,也会大幅提升代码的执行效率。这是一个非常不错的方式,也被广泛应用php优化中。但是,自己编写的php业务拓展的缺点也很明显:
拓展开发耗时比较长,需求变更的时候修改也复杂,写得不好可能会影响web服务稳定性。(例如,在apache的worker模式下,多线程场景下挂掉,会影响同一个进程下的其他正常子线程。如果是多线程的web模式,编写拓展还需要支持线程安全)拓展在php版本升级的时候,可能需要做额外的兼容工作。人员变动后的维护和接手成本也比较高。实际上,在互联网一线企业中,更常见的解决方案,并非增加php拓展,而用c/c++独立写一个服务server,然后php通过socket和服务server通信来完成业务处理,并不将php本身和业务耦合在一起。
不过,web服务大部分的性能瓶颈都在网络传输和其他服务server的耗时上(例如mysql等),php执行的耗时在整体耗时的占用比例非常小,所以从业务角度来说,影响可能并不明显。
2. hhvm提升php执行性能的方式
hhvm提升php性能的途径,采用的方式就是替代zend引擎来生成和执行php的中间字节码(hhvm生成自己格式的中间字节码),执行时通过jit(just in time,即时编译是种软件优化技术,指在运行时才会去编译字节码为机器码)转为机器码执行。zend引擎默认做法,是先编译为opcode,然后再逐条执行,通常每条指令对应的是c语言级别的函数。如果我们产生大量重复的opcode(纯php写的代码和函数),对应的则是zend多次逐条执行这些c代码。而jit所做的则是更进一步,将大量重复执行的字节码在运行的时候编译为机器码,达到提高执行效率的目的。通常,触发jit的条件是代码或者函数被多次重复调用。
普通的php代码,因为无法固定变量的类型,需要额外添加判断类型的逻辑代码,这样php代码是不利于cpu执行和优化的。因此,hhvm通常需要用到hack写法(为了兼容某种特性而额外添加的技巧性质的代码)的php代码来“配合”,就是为了让变量类型固定,方便虚拟机编译执行。php追求以一种形式来容纳一切类型,而hack则可以将被容纳的一切标记上确定的类型。
php代码的hack写法的例子:
上面的例子中,php代码主要被添加上了变量类型。hack写法的总体方向,就是将之前“动态”的写法变为“静态”的写法,来配合hhvm。
hhvm因为它的高性能而吸引了不少人的关注,一些一线互联网公司也开始跟进使用。从纯语言执行性能测试结果来看,hhvm领先了开发中的php7版本不少。
不过,从具体业务场景来看,hhvm和php7的差距并没有那么大,以wordpress开源博客首页为测试场景的结果中,他们目前的差距并不明显。
但是,php7目前还在开发中,就已经可用的技术方案来看,目前的hhvm略胜一筹。不过,hhvm的部署和应用都存在一些的问题:
服务部署比较复杂,有一定维护成本。对php原生代码并非完整支持,php拓展也需要做适当的兼容。hhvm是个新虚拟机,长时间运行有内存泄露。(据说,一线互联网公司在应用这个技术时,是通过自己打patch的方式解决内存泄露)hhvm毕竟是一个相对比较新的开源项目,发展到成熟仍然需要一定时间。
php7的性能革新php长期以来饱受批评的性能问题,将会在这个版本得到大幅度的改善。版本中间没有php6哈,据说,是因为这个版本曾经立过项目,后来大部分功能都在5.x的版本里实现了,为了避免混淆,下一个大版本直接就是php7。(几年以前,我还看到过关于php6的书籍。)
1. php7