Hack on HHVM —— Facebook是如何優化PHP的

Facebook週四正式發佈了Hack編程語言,將靜態類型以及一些現代的語言特性引入了PHP。這是Facebook對PHP優化之路上的新里程碑。php

請輸入圖片描述

Facebook爲什麼要優化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

Facebook要如何優化PHP

優化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相似的項目有Roadsendphc,前者已經不維護了,後者也是命運坎坷。

編譯到C++的效果很差,因此Facebook最終決定,仍是寫一個VM吧。

HHVM

FaceBook開發HHVM的陣容至關豪華,其中包括

  • Andrei Alexandrescu, 《C++ Coding Standards》的做者。
  • Drew Paroski,改進了.NET虛擬機的JIT。
  • Jason Evans,jemalloc的開發者(jemalloc將Firefox的內存消耗下降了一半)。
  • Keith Adams,VMware核心架構。
  • Sara Golemon,《Extending and Embedding PHP》做者,PHP內核領域的專家。

值得注意的是,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但願支持類型推導的閉包有關。

Hack

上面提到了類型推導。事實上,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

相關文章
相關標籤/搜索