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