HHVM 是 Facebook 開發的高性能 PHP 虛擬機,宣稱比官方的快9倍,我很好奇,于是抽空簡單了解了一下,并整理出這篇文章,希望能回答清楚兩方面的問題:
HHVM 到底靠譜么?是否可以用到產品中?
它為什么比官方的 PHP 快很多?到底是如何優化的?
在討論 HHVM 實現原理前,我們先設身處地想想:假設你有個 PHP 寫的網站遇到了性能問題,經分析后發現很大一部分資源就耗在 PHP 上,這時你會怎么優化 PHP 性能?
比如可以有以下幾種方式:
方案1,遷移到性能更好的語言上,如 Java、C++、Go。
方案2,通過 RPC 將功能分離出來用其它語言實現,讓 PHP 做更少的事情,比如 Twitter 就將大量業務邏輯放到了 Scala 中,前端的 Rails 只負責展現。
方案3,寫 PHP 擴展,在性能瓶頸地方換 C/C++。
方案4,優化 PHP 的性能。
方案1幾乎不可行,十年前 Joel 就拿 Netscape 的例子警告過,你將放棄是多年的經驗積累,尤其是像 Facebook 這種業務邏輯復雜的產品,PHP 代碼實在太多了,據稱有2千萬行(引用自 [PHP on the Metal with HHVM]),修改起來的成本恐怕比寫個虛擬機還大,而且對于一個上千人的團隊,從頭開始學習也是不可接受的。
方案2是最保險的方案,可以逐步遷移,事實上 Facebook 也在朝這方面努力了,而且還開發了 Thrift 這樣的 RPC 解決方案,Facebook 內部主要使用的另一個語言是 C++,從早期的 Thrift 代碼就能看出來,因為其它語言的實現都很簡陋,沒法在生產環境下使用。
目前在 Facebook 中據稱 PHP:C++ 已經從 9:1 增加到 7:3 了,加上有 Andrei Alexandrescu 的存在,C++ 在 Facebook 中越來越流行,但這只能解決部分問題,畢竟 C++ 開發成本比 PHP 高得多,不適合用在經常修改的地方,而且太多 RPC 的調用也會嚴重影響性能。
方案3看起來美好,實際執行起來卻很難,一般來說性能瓶頸并不會很顯著,大多是不斷累加的結果,加上 PHP 擴展開發成本高,這種方案一般只用在公共且變化不大的基礎庫上,所以這種方案解決不了多少問題。
可以看到,前面3個方案并不能很好地解決問題,所以 Facebook 其實沒有選擇的余地,只能去考慮 PHP 本身的優化了。
既然要優化 PHP,那如何去優化呢?在我看來可以有以下幾種方法:
方案1,PHP 語言層面的優化。
方案2,優化 PHP 的官方實現(也就是 Zend)。
方案3,將 PHP 編譯成其它語言的 bytecode(字節碼),借助其它語言的虛擬機(如 JVM)來運行。
方案4,將 PHP 轉成 C/C++,然后編譯成本地代碼。
方案5,開發更快的 PHP 虛擬機。
PHP 語言層面的優化是最簡單可行的,Facebook 當然想到了,而且還開發了 XHProf 這樣的性能分析工具,對于定位性能瓶頸是很有幫助的。
不過 XHProf 還是沒能很好解決 Facebook 的問題,所以我們繼續看,接下來是方案2,簡單來看,Zend 的執行過程可以分為兩部分:將 PHP 編譯為 opcode、執行 opcode,所以優化 Zend 可以從這兩方面來考慮。
優化 opcode 是一種常見的做法,可以避免重復解析 PHP,而且還能做一些靜態的編譯優化,比如 Zend Optimizer Plus,但由于 PHP 語言的動態性,這種優化方法是有局限性的,樂觀估計也只能提升20%的性能。另一種考慮是優化 opcode 架構本身,如基于寄存器的方式,但這種做法修改起來工作量太大,性能提升也不會特別明顯(可能30%?),所以投入產出比不高。
另一個方法是優化 opcode 的執行,首先簡單提一下 Zend 是如何執行的,Zend 的 interpreter(也叫解釋器)在讀到 opcode 后,會根據不同的 opcode 調用不同函數(其實有些是 switch,不過為了描述方便我簡化了),然后在這個函數中執行各種語言相關的操作(感興趣的話可看看深入理解 PHP 內核這本書),所以 Zend 中并沒有什么復雜封裝和間接調用,作為一個解釋器來說已經做得很好了。
想要提升 Zend 的執行性能,就需要對程序的底層執行有所解,比如函數調用其實是有開銷的,所以能通過 Inline threading 來優化掉,它的原理就像 C 語言中的 inline 關鍵字那樣,但它是在運行時將相關的函數展開,然后依次執行(只是打個比方,實際實現不太一樣),同時還避免了 CPU 流水線預測失敗導致的浪費。
另外還可以像 JavaScriptCore 和 LuaJIT 那樣使用匯編來實現 interpreter,具體細節建議看看 Mike 的解釋
但這兩種做法修改代價太大,甚至比重寫一個還難,尤其是要保證向下兼容,后面提到 PHP 的特點時你就知道了。
開發一個高性能的虛擬機不是件簡單的事情,JVM 花了10多年才達到現在的性能,那是否能直接利用這些高性能的虛擬機來優化 PHP 的性能呢?這就是方案3的思路。
其實這種方案早就有人嘗試過了,比如 Quercus 和 IBM 的 P8,Quercus 幾乎沒見有人使用,而 P8 也已經死掉了。Facebook 也曾經調研過這種方式,甚至還出現過不靠譜的傳聞 ,但其實 Facebook 在2011年就放棄了。
因為方案3看起來美好,但實際效果卻不理想,按照很多大牛的說法(比如 Mike),VM 總是為某個語言優化的,其它語言在上面實現會遇到很多瓶頸,比如動態的方法調用,關于這點在 Dart 的文檔中有過介紹,而且據說 Quercus 的性能與 Zend+APC 比差不了太多([來自The HipHop Compiler for PHP]),所以沒太大意義。
上一篇 foreach 結構
下一篇 WordPres介紹