Facebook周四正式发布了Hack编程语言,将静态类型以及一些现代的语言特性引入了PHP。这是Facebook对PHP优化之路上的新里程碑。php
这个问题能够从不一样角度来回答。简单直接的回答是,Facebook的规模太大了。PHP的性能问题限制了Facebook的发展。从另外一个角度来回答,则是要回答既然PHP不够用,为何不干脆换掉?html
把PHP换掉也有“总体换”和“局部换”的区别。最完全的方案就是彻底离开PHP,用别的语言重写一套。可是对于Facebook而言这个代价过高了。若是切换的话,多年来在PHP的积累就彻底做废了。并且Facebook的业务逻辑很是复杂,听说PHP代码有2千万行…… 并且,Facebook员工众多,切换到一种新的语言,学习成本也不低。git
既然总体换不可行,那就局部换吧。例如给PHP写C/C++扩展,能够提高性能。可是PHP扩展开发起来成本高,通常只适用于比较稳定的库,适用范围颇有限。另外一个方案将性能瓶颈的地方用其余语言实现,而后经过RPC(Remote Procedure Call,远程过程调用)在PHP和其余语言之间通信。Twitter就用了这条路线,大量组件使用Scala和Java编写,经过RPC与展示层的Rails通信。事实上,Facebook在这方面已经作了很多工做,为了减小RPC调用的开销,Facebook还专门开发了Thrift。然而,C++开发成本比PHP高不少,不适合用在须要快速修改的地方,并且大量RPC调用终究会影响性能。程序员
总体换不现实,Thrift不够用,那么Facebook优化PHP就势在必行了。github
优化PHP,最早想到的是做性能分析,找出瓶颈,而后进行对应的优化。Facebook为此开发了XHProf工具。XHProf精确到函数层面,数据收集组件使用C开发(PHP扩展),报告组件用了PHP。支持PHP 5.2以上版本,对于定位性能瓶颈颇有帮助。apache
可是PHP语言层面的优化限制太多,对Facebook而言仍是不够用。因此Facebook须要对PHP语言的实现自己进行优化。编程
首先能够考虑的方案是改善PHP的官方实现。PHP的官方解释器运行PHP代码的过程能够分为两步:第一步,将PHP编译为bytecode;第二步,运行bytecode。那么改善PHP的官方实现就能够从这两个方面着手。segmentfault
首先是优化编译PHP的步骤,这方面的工做已经有ZendOptimizerPlus作了。它会在内存中缓存编译好的bytecode,这样之后访问代码的时候就能够直接访问缓存好了的bytecode,省去了从磁盘读取再从新编译的开销。可是因为PHP语言的动态性,这个方法的效果通常,最好的状况下也只能提高20%的性能。缓存
其次是优化运行bytecode的步骤。上面提到的ZendOptimizerPlus主要是优化编译PHP,可是也附带作了一些bytecode运行的优化。PHP有三种方式来运行bytecode:CALL、SWITCH和GOTO,默认使用CALL,也就是函数调用。优化函数调用,经常使用的方法就是内联(Inline function),也就是将函数展开,将函数体插入替换调用该函数的地方,这样能够节省每次调用函数带来的额外时间开支。可是这种作法实际上是用“空间换时间”,若是内联过头了,空间开销会很大,得不偿失。在这方面进行调整,能够提高运行bytecode的性能。服务器
此外还能够将整个PHP解释器用汇编重写,以快闻名的LuaJIT就是这么干的。
然而,不管是内联优化仍是用汇编重写,代价都很大,并且若是优化官方实现的话,还要考虑PHP的向下兼容……
既然这个方案不太现实,那么不如把PHP搬到JVM上吧?JVM性能至关不错。
把PHP搬到JVM的工做,有人已经作过了。例如,IBM的P8(已死)和Quercus(半死不活)。Facebook也研究过这个方案,2012年的时候,还有Facebook迁移到JVM的传闻。其实Facebook早已放弃这条路线。根据Facebook的研究,Quercus的性能和Zend+APC相比,差不了太多。这一方案效果不理想的缘由多是,JVM自己性能的优化是针对Java作的,其余语言在JVM上实现,不必定能用到这些优化。动态语言尤其如此。由于Java自己是静态类型的,因此不少优化JVM就不必作,而在JVM上跑的动态语言须要这些优化。
既然JVM是为Java优化的,搬上去不合适,那不如针对PHP开发一个VM?这样就能够做大量针对性地优化了。然而开发VM可没有那么容易,成本不小,因此Facebook最初的选择是将PHP编译成C/C++之类性能优异的语言。也就是HHVM的前身——HPHPc。具体的作法是将PHP翻译为C++,而后再编译。
相比VM,这样的实现比较简单,并且能放手作优化(由于是离线编译,因此能够用时间换性能)。可是PHP的不少动态内容编译成C++比较麻烦,所以HPHPc禁掉了eval()
之类的特性,即便这样,仍是带来了一些问题,特别是因为须要将动态include的文件都编译在一块儿,最终的部署文件体积太庞大了,都过G了。
和HPHPc相似的项目有Roadsend和phc,前者已经不维护了,后者也是命运坎坷。
编译到C++的效果很差,因此Facebook最终决定,仍是写一个VM吧。
FaceBook开发HHVM的阵容至关豪华,其中包括
值得注意的是,Keith Adams给HHVM的影响很大。HHVM使用了JIT技术,通常的代码经过解释器执行(由于JIT也是有开销的),而经常使用的代码则使用JIT优化。一般而言,VM判断是否须要进行JIT优化是经过如下两种策略的一种:method-at-a-time(若是函数的执行超过了阈值,就进行JIT优化)和tracing (若是循环的执行超过了阈值,就进行JIT优化)。可是HHVM使用的是一种独特的策略,basic-block-at-a-time,这个策略和VMware的x86 hypervisor类似。使用这个策略与Facebook但愿支持类型推导的闭包有关。
上面提到了类型推导。事实上,Facebook推出了一个运行在HHVM上的PHP改良语言——Hack。Hack里加入了类型的支持:
<?hh class MyClass { const int MyConst = 0; private string $x = ''; public function increment(int $x): int { $y = $x + 1; return $y; } }
加了类型以后,除了方便大型团队协做,避免编程中出现的错误以外,还有一个重要的缘由就是可以让HHVM更好地优化性能。JIT优化最主要的方面就是根据类型来生成特定的指令,这样能够减小大量的指令和条件判断。而对于PHP这样的动态语言,要推断清楚类型是很是困难的,因此Hack就直接让程序员写上了。
HHVM除了做为Hack的VM以外,还能够运行原生的PHP。兼容性测试代表,HHVM对PHP的兼容度已经达到98.58%了。因为HHVM使用了独特的JIT优化策略,所以Facebook自行开发了tracelet辅助库,这个库只支持x86 64bit系统,因此HHVM也只能在64位系统上使用——不过这个问题不大,如今的服务器硬件基本都支持64位了。须要考虑的是PHP扩展的问题。因为PHP语言包含很是之多的扩展,而Facebook的HHVM只实现了自家用到的扩展,因此可能有为HHVM重写PHP扩展的须要。好在相比为官方PHP实现写扩展,为HHVM写扩展比较容易,对性能要求不高的扩展可使用纯PHP编写,而后编译到HHVM二进制文件中便可,详见HHVM wiki。还有一个要当心的问题就是HHVM是常驻内存的,因此若是某处PHP代码有内存泄露问题的话,可能拖慢整个HHVM服务的速度,甚至致使HHVM挂掉。
编撰 SegmentFault