国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

目錄
背景
你會(huì)怎么做?
更快的 PHP
更快的虛擬機(jī)
規(guī)范是什么?
語(yǔ)言還是擴(kuò)展?
實(shí)現(xiàn) Interpreter
實(shí)現(xiàn) JIT 及優(yōu)化
類(lèi)型推導(dǎo)很麻煩,還是逼迫程序員寫(xiě)清楚吧
你會(huì)使用 HHVM 么?
引用
首頁(yè) php教程 php手冊(cè) HHVM 是如何提升 PHP 性能的[轉(zhuǎn)]

HHVM 是如何提升 PHP 性能的[轉(zhuǎn)]

Jun 06, 2016 pm 08:14 PM
hhvm php 如何 效能 提升 背景

背景 HHVM 是 Facebook 開(kāi)發(fā)的高性能 PHP 虛擬機(jī),宣稱比官方的快9倍,我很好奇,于是抽空簡(jiǎn)單了解了一下,并整理出這篇文章,希望能回答清楚兩方面的問(wèn)題 HHVM 到底靠譜么?是否可以用到產(chǎn)品中? 它為什么比官方的 PHP 快很多?到底是如何優(yōu)化的? 你會(huì)怎么

背景

HHVM 是 Facebook 開(kāi)發(fā)的高性能 PHP 虛擬機(jī),宣稱比官方的快9倍,我很好奇,于是抽空簡(jiǎn)單了解了一下,并整理出這篇文章,希望能回答清楚兩方面的問(wèn)題

  • HHVM 到底靠譜么?是否可以用到產(chǎn)品中?
  • 它為什么比官方的 PHP 快很多?到底是如何優(yōu)化的?

你會(huì)怎么做?

在討論 HHVM 實(shí)現(xiàn)原理前,我們先設(shè)身處地想想:假設(shè)你有個(gè) PHP 寫(xiě)的網(wǎng)站遇到了性能問(wèn)題,經(jīng)分析后發(fā)現(xiàn)很大一部分資源就耗在 PHP 上,這時(shí)你會(huì)怎么優(yōu)化 PHP 性能?

比如可以有以下幾種方式:

  • 方案1,遷移到性能更好的語(yǔ)言上,如 Java、C++、Go。
  • 方案2,通過(guò) RPC 將功能分離出來(lái)用其它語(yǔ)言實(shí)現(xiàn),讓 PHP 做更少的事情,比如 Twitter 就將大量業(yè)務(wù)邏輯放到了 Scala 中,前端的 Rails 只負(fù)責(zé)展現(xiàn)。
  • 方案3,寫(xiě) PHP 擴(kuò)展,在性能瓶頸地方換 C/C++。
  • 方案4,優(yōu)化 PHP 的性能

方案1幾乎不可行,十年前 Joel 就拿Netscape的例子警告過(guò),你將放棄是多年的經(jīng)驗(yàn)積累,尤其是像 Facebook 這種業(yè)務(wù)邏輯復(fù)雜的產(chǎn)品,PHP 代碼實(shí)在太多了,據(jù)稱有2千萬(wàn)行(引用自 [PHP on the Metal with HHVM]),修改起來(lái)的成本恐怕比寫(xiě)個(gè)虛擬機(jī)還大,而且對(duì)于一個(gè)上千人的團(tuán)隊(duì),從頭開(kāi)始學(xué)習(xí)也是不可接受的。

方案2是最保險(xiǎn)的方案,可以逐步遷移,事實(shí)上 Facebook 也在朝這方面努力了,而且還開(kāi)發(fā)了 Thrift 這樣的 RPC 解決方案,F(xiàn)acebook 內(nèi)部主要使用的另一個(gè)語(yǔ)言是 C++,從早期的 Thrift 代碼就能看出來(lái),因?yàn)槠渌Z(yǔ)言的實(shí)現(xiàn)都很簡(jiǎn)陋,沒(méi)法在生產(chǎn)環(huán)境下使用。

目前在 Facebook 中據(jù)稱 PHP:C++ 已經(jīng)從9:1增加到7:3了,加上有 Andrei Alexandrescu 的存在,C++ 在 Facebook 中越來(lái)越流行,但這只能解決部分問(wèn)題,畢竟 C++ 開(kāi)發(fā)成本比 PHP 高得多,不適合用在經(jīng)常修改的地方,而且太多 RPC 的調(diào)用也會(huì)嚴(yán)重影響性能。

方案3看起來(lái)美好,實(shí)際執(zhí)行起來(lái)卻很難,一般來(lái)說(shuō)性能瓶頸并不會(huì)很顯著,大多是不斷累加的結(jié)果,加上 PHP 擴(kuò)展開(kāi)發(fā)成本高,這種方案一般只用在公共且變化不大的基礎(chǔ)庫(kù)上,所以這種方案解決不了多少問(wèn)題。

可以看到,前面3個(gè)方案并不能很好地解決問(wèn)題,所以 Facebook 其實(shí)沒(méi)有選擇的余地,只能去考慮 PHP 本身的優(yōu)化了。

更快的 PHP

既然要優(yōu)化 PHP,那如何去優(yōu)化呢?在我看來(lái)可以有以下幾種方法

  • 方案1,PHP 語(yǔ)言層面的優(yōu)化。
  • 方案2,優(yōu)化 PHP 的官方實(shí)現(xiàn)(也就是 Zend)。
  • 方案3,將 PHP 編譯成其它語(yǔ)言的 bytecode(字節(jié)碼),借助其它語(yǔ)言的虛擬機(jī)(如 JVM)來(lái)運(yùn)行。
  • 方案4,將 PHP 轉(zhuǎn)成 C/C++,然后編譯成本地代碼。
  • 方案5,開(kāi)發(fā)更快的 PHP 虛擬機(jī)。

PHP 語(yǔ)言層面的優(yōu)化是最簡(jiǎn)單可行的,F(xiàn)acebook 當(dāng)然想到了,而且還開(kāi)發(fā)了XHProf這樣的性能分析工具,對(duì)于定位性能瓶頸是很有幫助的。

不過(guò) XHProf 還是沒(méi)能很好解決 Facebook 的問(wèn)題,所以我們繼續(xù)看,接下來(lái)是方案2,簡(jiǎn)單來(lái)看,Zend 的執(zhí)行過(guò)程可以分為兩部分:將 PHP 編譯為 opcode、執(zhí)行 opcode,所以優(yōu)化 Zend 可以從這兩方面來(lái)考慮。

優(yōu)化 opcode 是一種常見(jiàn)的做法,可以避免重復(fù)解析 PHP,而且還能做一些靜態(tài)的編譯優(yōu)化,比如Zend Optimizer Plus,但由于 PHP 語(yǔ)言的動(dòng)態(tài)性,這種優(yōu)化方法是有局限性的,樂(lè)觀估計(jì)也只能提升20%的性能。另一種考慮是優(yōu)化 opcode 架構(gòu)本身,如基于寄存器的方式,但這種做法修改起來(lái)工作量太大,性能提升也不會(huì)特別明顯(可能30%?),所以投入產(chǎn)出比不高。

另一個(gè)方法是優(yōu)化 opcode 的執(zhí)行,首先簡(jiǎn)單提一下 Zend 是如何執(zhí)行的,Zend 的 interpreter(也叫解釋器)在讀到 opcode 后,會(huì)根據(jù)不同的 opcode 調(diào)用不同函數(shù)(其實(shí)有些是 switch,不過(guò)為了描述方便我簡(jiǎn)化了),然后在這個(gè)函數(shù)中執(zhí)行各種語(yǔ)言相關(guān)的操作(感興趣的話可看看深入理解PHP內(nèi)核這本書(shū)),所以 Zend 中并沒(méi)有什么復(fù)雜封裝和間接調(diào)用,作為一個(gè)解釋器來(lái)說(shuō)已經(jīng)做得很好了。

想要提升 Zend 的執(zhí)行性能,就需要對(duì)程序的底層執(zhí)行有所解,比如函數(shù)調(diào)用其實(shí)是有開(kāi)銷(xiāo)的,所以能通過(guò) Inline threading 來(lái)優(yōu)化掉,它的原理就像 C 語(yǔ)言中的 inline 關(guān)鍵字那樣,但它是在運(yùn)行時(shí)將相關(guān)的函數(shù)展開(kāi),然后依次執(zhí)行(只是打個(gè)比方,實(shí)際實(shí)現(xiàn)不太一樣),同時(shí)還避免了 CPU 流水線預(yù)測(cè)失敗導(dǎo)致的浪費(fèi)。

另外還可以像 JavaScriptCore 和 LuaJIT 那樣使用匯編來(lái)實(shí)現(xiàn) interpreter,具體細(xì)節(jié)建議看看 Mike的解釋

但這兩種做法修改代價(jià)太大,甚至比重寫(xiě)一個(gè)還難,尤其是要保證向下兼容,后面提到 PHP 的特點(diǎn)時(shí)你就知道了。

開(kāi)發(fā)一個(gè)高性能的虛擬機(jī)不是件簡(jiǎn)單的事情,JVM 花了10多年才達(dá)到現(xiàn)在的性能,那是否能直接利用這些高性能的虛擬機(jī)來(lái)優(yōu)化 PHP 的性能呢?這就是方案3的思路。

其實(shí)這種方案早就有人嘗試過(guò)了,比如 Quercus 和 IBM 的 P8,Quercus 幾乎沒(méi)見(jiàn)有人使用,而 P8 也已經(jīng)死掉了。Facebook 也曾經(jīng)調(diào)研過(guò)這種方式,甚至還出現(xiàn)過(guò)不靠譜的傳聞 ,但其實(shí) Facebook 在2011年就放棄了。

因?yàn)榉桨?看起來(lái)美好,但實(shí)際效果卻不理想,按照很多大牛的說(shuō)法(比如 Mike),VM 總是為某個(gè)語(yǔ)言優(yōu)化的,其它語(yǔ)言在上面實(shí)現(xiàn)會(huì)遇到很多瓶頸,比如動(dòng)態(tài)的方法調(diào)用,關(guān)于這點(diǎn)在 Dart的文檔中有過(guò)介紹,而且據(jù)說(shuō) Quercus 的性能與 Zend+APC 比差不了太多([來(lái)自The HipHop Compiler for PHP]),所以沒(méi)太大意義。

不過(guò) OpenJDK 這幾年也在努力,最近的 Grall 項(xiàng)目看起來(lái)還不錯(cuò),也有語(yǔ)言在上面取得了顯著的效果,但我還沒(méi)空研究 Grall,所以這里無(wú)法判斷。

接下來(lái)是方案4,它正是 HPHPc(HHVM 的前身)的做法,原理是將 PHP 代碼轉(zhuǎn)成 C++,然后編譯為本地文件,可以認(rèn)為是一種 AOT(ahead of time)的方式,關(guān)于其中代碼轉(zhuǎn)換的技術(shù)細(xì)節(jié)可以參考 The HipHop Compiler for PHP這篇論文,以下是該論文中的一個(gè)截圖,可以通過(guò)它來(lái)大概了解:

這種做法的最大優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單(相對(duì)于一個(gè) VM 來(lái)說(shuō)),而且能做很多編譯優(yōu)化(因?yàn)槭请x線的,慢點(diǎn)也沒(méi)事),比如上面的例子就將- 1優(yōu)化掉了,但它很難支持 PHP 中的很多動(dòng)態(tài)的方法,如 eval()create_function(),因?yàn)檫@就得再內(nèi)嵌一個(gè) interpreter,成本不小,所以 HPHPc 干脆就直接不支持這些語(yǔ)法。

除了 HPHPc,還有兩個(gè)類(lèi)似的項(xiàng)目,一個(gè)是 Roadsend,另一個(gè)是 phc ,phc 的做法是將 PHP 轉(zhuǎn)成了 C 再編譯,以下是它將 file_get_contents($f) 轉(zhuǎn)成 C 代碼的例子

<code>static php_fcall_info fgc_info;
php_fcall_info_init ("file_get_contents", &fgc_info);
php_hash_find (LOCAL_ST, "f", 5863275, &fgc_info.params);
php_call_function (&fgc_info);
</code>

話說(shuō) phc作者曾經(jīng)在博客上哭訴,說(shuō)他兩年前就去 Facebook 演示過(guò) phc 了,還和那里的工程師交流過(guò),結(jié)果人家一發(fā)布就火了,而自己忙活了4年卻默默無(wú)聞,現(xiàn)在前途渺茫。。。

Roadsend 也已經(jīng)不維護(hù)了,對(duì)于 PHP 這樣的動(dòng)態(tài)語(yǔ)言來(lái)說(shuō),這種做法有很多的局限性,由于無(wú)法動(dòng)態(tài) include,F(xiàn)acebook 將所有文件都編譯到了一起,上線時(shí)的文件部署居然達(dá)到了 1G,越來(lái)越不可接受了。

另外有還有一個(gè)叫 PHP QB 的項(xiàng)目,由于時(shí)間關(guān)系我沒(méi)有看,感覺(jué)可能是類(lèi)似的東東。

所以就只剩下一條路了,那就是寫(xiě)一個(gè)更快的 PHP 虛擬機(jī),將一條黑路走到底,或許你和我一樣,一開(kāi)始聽(tīng)到 Facebook 要做一個(gè)虛擬機(jī)是覺(jué)得太離譜,但如果仔細(xì)分析就會(huì)發(fā)現(xiàn)其實(shí)也只有這樣了。

更快的虛擬機(jī)

HHVM 為什么更快?在各種新聞報(bào)道中都提到了 JIT 這個(gè)關(guān)鍵技術(shù),但其實(shí)遠(yuǎn)沒(méi)有那么簡(jiǎn)單,JIT 不是什么神奇的魔法棒,用它輕輕一揮就能提升性能,而且 JIT 這個(gè)操作本身也是會(huì)耗時(shí)的,對(duì)于簡(jiǎn)單的程序沒(méi)準(zhǔn)還比 interpreter 慢,最極端的例子是 LuaJIT 2 的 Interpreter 就稍微比 V8 的 JIT 快,所以并不存在絕對(duì)的事情,更多還是在細(xì)節(jié)問(wèn)題的處理上,HHVM 的發(fā)展歷史就是不斷優(yōu)化的歷史,你可以從下圖看到它是如何一點(diǎn)點(diǎn)超過(guò) HPHPc 的:

值得一提的是在 Android 4.4 中新的虛擬機(jī) ART 就采用的是 AOT 方案(還記得么?前面提到的 HPHPc 就是這種),結(jié)果比之前使用 JIT 的 Dalvik 快了一倍,所以說(shuō) JIT 也不一定比 AOT 快。

因此這個(gè)項(xiàng)目是有很大風(fēng)險(xiǎn)的,如果沒(méi)有強(qiáng)大的內(nèi)心和毅力,極有可能半途而廢,Google就曾經(jīng)想用 JIT 提升Python 的性能,但最終失敗了,對(duì)于 Google 來(lái)說(shuō)用到 Python 的地方其實(shí)并沒(méi)什么性能問(wèn)題(好吧,以前 Google 是用 Python 寫(xiě)過(guò) crawl [參考 In The Plex],但那都是1996年的事情了)。

比起 Google,F(xiàn)acebook 顯然有更大的動(dòng)力和決心,PHP 是 Facebook 最重要的語(yǔ)言,我們來(lái)看看 Facebook 都投入了哪些大牛到這個(gè)項(xiàng)目中(不全)

  • Andrei Alexandrescu,『Modern C++ Design』和『C++ Coding Standards』的作者,C++ 領(lǐng)域無(wú)可爭(zhēng)議的大神
  • Keith Adams,負(fù)責(zé)過(guò) VMware 核心架構(gòu),當(dāng)年 VMware 就派他一人去和 Intel 進(jìn)行技術(shù)合作,足以證明在 VMM 領(lǐng)域他有多了解了
  • Drew Paroski,在微軟參與過(guò) .NET 虛擬機(jī)開(kāi)發(fā),改進(jìn)了其中的 JIT
  • Jason Evans,開(kāi)發(fā)了 jemalloc,減少了 Firefox 一半的內(nèi)存消耗
  • Sara Golemon,『Extending and Embedding PHP』的作者,PHP 內(nèi)核專家,這本書(shū)估計(jì)所有 PHP 高手都看過(guò)吧,或許你不知道其實(shí)她是女的

雖然沒(méi)有像 Lars Bak、Mike Pall 這樣在虛擬機(jī)領(lǐng)域的頂級(jí)專家,但如果這些大牛能齊心協(xié)力,寫(xiě)個(gè)虛擬機(jī)還是問(wèn)題不大的,那么他們將面臨什么樣的挑戰(zhàn)呢?接下來(lái)我們一一討論。

規(guī)范是什么?

自己寫(xiě) PHP 虛擬機(jī)要面臨的第一個(gè)問(wèn)題就是 PHP 沒(méi)有語(yǔ)言規(guī)范,很多版本間的語(yǔ)法還會(huì)不兼容(甚至是小版本號(hào),比如 5.2.1 和 5.2.3),PHP 語(yǔ)言規(guī)范究竟如何定義呢?來(lái)看一篇來(lái)自 IEEE 的說(shuō)法:

The PHP group claim that they have the ?nal say in the speci?cation of (the language) PHP. This groups speci?cation is an implementation, and there is no prose speci?cation or agreed validation suite.

所以唯一的途徑就是老老實(shí)實(shí)去看 Zend 的實(shí)現(xiàn),好在 HPHPc 中已經(jīng)痛苦過(guò)一次了,所以 HHVM 能直接利用現(xiàn)成,因此這個(gè)問(wèn)題并不算太大。

語(yǔ)言還是擴(kuò)展?

實(shí)現(xiàn) PHP 語(yǔ)言不僅僅只是實(shí)現(xiàn)一個(gè)虛擬機(jī)那么簡(jiǎn)單,PHP 語(yǔ)言本身還包括了各種擴(kuò)展,這些擴(kuò)展和語(yǔ)言是一體的,Zend 不辭辛勞地實(shí)現(xiàn)了各種你可能會(huì)用到的功能。如果分析過(guò) PHP 的代碼,就會(huì)發(fā)現(xiàn)它的 C 代碼除去空行注釋后居然還有80+萬(wàn)行,而你猜其中 Zend 引擎部分有多少?只有不到10萬(wàn)行。

對(duì)于開(kāi)發(fā)者來(lái)說(shuō)這不是什么壞事,但對(duì)于引擎實(shí)現(xiàn)者來(lái)說(shuō)就很悲劇了,我們可以拿 Java 來(lái)進(jìn)行對(duì)比,寫(xiě)個(gè) Java 的虛擬機(jī)只需實(shí)現(xiàn)字節(jié)碼解釋及一些基礎(chǔ)的 JNI 調(diào)用,Java 絕大部分內(nèi)置庫(kù)都是用 Java 實(shí)現(xiàn)的,所以如果不考慮性能優(yōu)化,單從工作量看,實(shí)現(xiàn) PHP 虛擬機(jī)比 JVM 要難得多,比如就有人用8千行的 TypeScript 實(shí)現(xiàn)了一個(gè) JVM Doppio。

而對(duì)于這個(gè)問(wèn)題,HHVM 的解決辦法很簡(jiǎn)單,那就是只實(shí)現(xiàn) Facebook 中用到的,而且同樣可以先用 HPHPc 中之前寫(xiě)過(guò)的,所以問(wèn)題也不大。

實(shí)現(xiàn) Interpreter

接下來(lái)是 Interpreter 的實(shí)現(xiàn),在解析完 PHP 后會(huì)生成 HHVM 自己設(shè)計(jì)的一種 Bytecode,存儲(chǔ)在~/.hhvm.hhbc(SQLite 文件) 中以便重用,在執(zhí)行 Bytecode 時(shí)和 Zend 類(lèi)似,也是將不同的字節(jié)碼放到不同的函數(shù)中去實(shí)現(xiàn)(這種方式在虛擬機(jī)中有個(gè)專門(mén)的稱呼:Subroutine threading)

Interpreter 的主體實(shí)現(xiàn)在 bytecode.cpp 中,比如 VMExecutionContext::iopAdd 這樣的方法,最終執(zhí)行會(huì)根據(jù)不同類(lèi)型來(lái)區(qū)分,比如 add 操作的實(shí)現(xiàn)是在 tv-arith.cpp 中,下面摘抄其中的一小段

<code>if (c2.m_type == KindOfInt64)  return o(c1.m_data.num, c2.m_data.num);
if (c2.m_type == KindOfDouble) return o(c1.m_data.num, c2.m_data.dbl);
</code>

正是因?yàn)橛辛?Interpreter,HHVM 在對(duì)于 PHP 語(yǔ)法的支持上比 HPHPc 有明顯改進(jìn),理論上做到完全兼容官方 PHP,但僅這么做在性能并不會(huì)比 Zend 好多少,由于無(wú)法確定變量類(lèi)型,所以需要加上類(lèi)似上面的條件判斷語(yǔ)句,但這樣的代碼不利于現(xiàn)代 CPU 的執(zhí)行優(yōu)化,另一個(gè)問(wèn)題是數(shù)據(jù)都是 boxed 的,每次讀取都需要通過(guò)類(lèi)似 mdata.num 和 mdata.dbl 的方法來(lái)間接獲取。

對(duì)于這樣的問(wèn)題,就得靠 JIT 來(lái)優(yōu)化了

實(shí)現(xiàn) JIT 及優(yōu)化

首先值得一提的是 PHP 的 JIT 之前并非沒(méi)人嘗試過(guò)

  • 2008 年就有人用 LLVM實(shí)驗(yàn)過(guò),結(jié)果還比原來(lái)慢了 21 倍。。。
  • 2010 年 IBM 日本研究院基于他們的 JVM 虛擬機(jī)代碼開(kāi)發(fā)了 P9,性能是官方 PHP 的 2.5 到 9.5 倍,可以看他們的論文Evaluation of a just-in-time compiler retrofitted for PHP。
  • 2011 年 Andrei Homescu 基于 RPython 開(kāi)發(fā)過(guò),還寫(xiě)了篇論文 HappyJIT: a tracing JIT compiler for PHP,但測(cè)試結(jié)果有好有壞,并不理想。

那么究竟什么是 JIT?如何實(shí)現(xiàn)一個(gè) JIT?

在動(dòng)態(tài)語(yǔ)言中基本上都會(huì)有個(gè) eval 方法,可以傳給它一段字符串來(lái)執(zhí)行,JIT 做的就是類(lèi)似的事情,只不過(guò)它要拼接不是字符串,而是不同平臺(tái)下的機(jī)器碼,然后進(jìn)行執(zhí)行,但如何用 C 來(lái)實(shí)現(xiàn)呢?可以參考 Eli 寫(xiě)的這個(gè)入門(mén)例子,以下是文中的一段代碼:

<code>unsigned char code[] = {
  0x48, 0x89, 0xf8,                   // mov %rdi, %rax
  0x48, 0x83, 0xc0, 0x04,             // add $4, %rax
  0xc3                                // ret
};
memcpy(m, code, sizeof(code));
</code>

然而手工編寫(xiě)機(jī)器碼很容易出錯(cuò),所以最好的有一個(gè)輔助的庫(kù),比如的 Mozilla 的 Nanojit 以及 LuaJIT 的 DynASM,但 HHVM 并沒(méi)有使用這些,而是自己實(shí)現(xiàn)了一個(gè)只支持 x64 的(另外還在嘗試用 VIXL 來(lái)生成 ARM 64 位的),通過(guò) mprotect 的方式來(lái)讓代碼可執(zhí)行。

但為什么 JIT 代碼會(huì)更快?你可以想想其實(shí)用 C++ 編寫(xiě)的代碼最終編譯出來(lái)也是機(jī)器碼,如果只是將同樣的代碼手動(dòng)轉(zhuǎn)成了機(jī)器碼,那和 GCC 生成出來(lái)的有什么區(qū)別呢?雖然前面我們提到了一些針對(duì) CPU 實(shí)現(xiàn)原理來(lái)優(yōu)化的技巧,但在 JIT 中更重要的優(yōu)化是根據(jù)類(lèi)型來(lái)生成特定的指令,從而大幅減少指令數(shù)和條件判斷,下面這張來(lái)自 TraceMonkey 的圖對(duì)此進(jìn)行了很直觀的對(duì)比,后面我們將看到 HHVM 中的具體例子:

HHVM 首先通過(guò) interpeter 來(lái)執(zhí)行,那它會(huì)在時(shí)候使用 JIT 呢?常見(jiàn)的 JIT 觸發(fā)條件有 2 種:

  • trace:記錄循環(huán)執(zhí)行次數(shù),如果超過(guò)一定數(shù)量就對(duì)這段代碼進(jìn)行 JIT
  • method:記錄函數(shù)執(zhí)行次數(shù),如果超過(guò)一定數(shù)量就對(duì)整個(gè)函數(shù)進(jìn)行 JIT,甚至直接 inline

關(guān)于這兩種方法哪種更好在 Lambada 上有個(gè)帖子引來(lái)了各路大神的討論,尤其是 Mike Pall(LuaJIT 作者) 、Andreas Gal(Mozilla VP) 和 Brendan Eich(Mozilla CTO)都發(fā)表了很多自己的觀點(diǎn),推薦大家圍觀,我這里就不獻(xiàn)丑了。

它們之間的區(qū)別不僅僅是編譯范圍,還有很多細(xì)節(jié)問(wèn)題,比如對(duì)局部變量的處理,在這里就不展開(kāi)了

但 HHVM 并沒(méi)有采用這兩種方式,而是自創(chuàng)了一個(gè)叫 tracelet 的做法,它是根據(jù)類(lèi)型來(lái)劃分的,看下面這張圖

可以看到它將一個(gè)函數(shù)劃分為了 3 部分,上面 2 部分是用于處理 $k 為整數(shù)或字符串兩種不同情況的,下面的部分是返回值,所以看起來(lái)它主要是根據(jù)類(lèi)型的變化情況來(lái)劃分 JIT 區(qū)域的,具體是如何分析和拆解 Tracelet 的細(xì)節(jié)可以查看 Translator.cpp 中的 Translator::analyze 方法,我還沒(méi)空看,這里就不討論了。

當(dāng)然,要實(shí)現(xiàn)高性能的 JIT 還需進(jìn)行各種嘗試和優(yōu)化,比如最初 HHVM 新增的 tracelet 會(huì)放到前面,也就是將上圖的 A 和 C 調(diào)換位置,后來(lái)嘗試了一下放到后面,結(jié)果性能提示了 14%,因?yàn)闇y(cè)試發(fā)現(xiàn)這樣更容易提前命中響應(yīng)的類(lèi)型

JIT 的執(zhí)行過(guò)程是首先將 HHBC 轉(zhuǎn)成 SSA (hhbc-translator.cpp),然后對(duì) SSA 上做優(yōu)化(比如 Copy propagation),再生成本地機(jī)器碼,比如在 X64 下是由 translator-x64.cpp 實(shí)現(xiàn)的。

我們用一個(gè)簡(jiǎn)單的例子來(lái)看看 HHVM 最終生成的機(jī)器碼是怎樣的,比如下面這個(gè) PHP 函數(shù):

<code><?php function a($b){
  echo $b + 2;
}
</code></code>

編譯后是這個(gè)樣子:

<code>mov rcx,0x7200000
mov rdi,rbp
mov rsi,rbx
mov rdx,0x20
call 0x2651dfb <:transl::tracecallback hphp::typedvalue long void>
cmp BYTE PTR [rbp-0x8],0xa
jne 0xae00306
; 前面是檢查參數(shù)是否有效
mov rcx,QWORD PTR [rbp-0x10]           ; 這里將 %rcx 被賦值為1了
mov edi,0x2                            ; 將 %edi(也就是 %rdi 的低32位)賦值為2
add rdi,rcx                            ; 加上 %rcx
call 0x2131f1b <:print_int> ; 調(diào)用 print_int 函數(shù),這時(shí)第一個(gè)參數(shù) %rdi 的值已經(jīng)是3了
; 后面暫不討論
mov BYTE PTR [rbp+0x28],0x8
lea rbx,[rbp+0x20]
test BYTE PTR [r12],0xff
jne 0xae0032a
push QWORD PTR [rbp+0x8]
mov rbp,QWORD PTR [rbp+0x0]
mov rdi,rbp
mov rsi,rbx
mov rdx,QWORD PTR [rsp]
call 0x236b70e <:jit::traceret hphp::typedvalue void>
ret
</:jit::traceret></:print_int></:transl::tracecallback></code>

而 HPHP::print_int 函數(shù)的實(shí)現(xiàn)是這樣的:

<code>void print_int(int64_t i) {
  char buf[256];
  snprintf(buf, 256, "%" PRId64, i);
  echo(buf);
  TRACE(1, "t-x64 output(int): %" PRId64 "\n", i);
}
</code>

可以看到 HHVM 編譯出來(lái)的代碼直接使用了 int64_t,避免了 interpreter 中需要判斷參數(shù)和間接取數(shù)據(jù)的問(wèn)題,從而明顯提升了性能,最終甚至做到了和 C 編譯出來(lái)的代碼區(qū)別不大。

需要注意:HHVM 在 server mode 下,只有超過(guò)12個(gè)請(qǐng)求就才會(huì)觸發(fā) JIT,啟動(dòng)過(guò) HHVM 時(shí)可以通過(guò)加上如下參數(shù)來(lái)讓它首次請(qǐng)求就使用 JIT:

<code>-v Eval.JitWarmupRequests=0
</code>

所以在測(cè)試性能時(shí)需要注意,運(yùn)行一兩次就拿來(lái)對(duì)比是看不出效果的。

類(lèi)型推導(dǎo)很麻煩,還是逼迫程序員寫(xiě)清楚吧

JIT 的關(guān)鍵是猜測(cè)類(lèi)型,因此某個(gè)變量的類(lèi)型要是老變就很難優(yōu)化,于是 HHVM 的工程師開(kāi)始考慮在 PHP 語(yǔ)法上做手腳,加上類(lèi)型的支持,推出了一個(gè)新語(yǔ)言 - Hack(吐槽一下這名字真不利于 SEO),它的樣子如下:

<code><?php class Point2 {
  public float $x, $y;
  function __construct(float $x, float $y) {
    $this->x = $x;
    $this->y = $y;
  }
}
//來(lái)自:https://raw.github.com/strangeloop/StrangeLoop2013/master/slides/sessions/Adams-TakingPHPSeriously.pdf
</code>

注意到 float 關(guān)鍵字了么?有了靜態(tài)類(lèi)型可以讓 HHVM 更好地優(yōu)化性能,但這也意味著和 PHP 語(yǔ)法不兼容,只能使用 HHVM。

其實(shí)我個(gè)人認(rèn)為這樣做最大的優(yōu)點(diǎn)是讓代碼更加易懂,減少無(wú)意的犯錯(cuò),就像 Dart 中的可選類(lèi)型也是這個(gè)初衷,同時(shí)還方便了 IDE 識(shí)別,據(jù)說(shuō) Facebook 還在開(kāi)發(fā)一個(gè)基于Web的IDE,能協(xié)同編輯代碼,可以期待一下

你會(huì)使用 HHVM 么?

總的來(lái)說(shuō),比起之前的 HPHPc,我認(rèn)為 HHVM 是值得一試的,它是真正的虛擬機(jī),能夠更好地支持各種 PHP 的語(yǔ)法,所以改動(dòng)成本不會(huì)更高,而且因?yàn)槟軣o(wú)縫切換到官方 PHP 版本,所以可以同時(shí)啟動(dòng) FPM 來(lái)隨時(shí)待命,HHVM 還有 FastCGI 接口方便調(diào)用,只要做好應(yīng)急備案,風(fēng)險(xiǎn)是可控的,從長(zhǎng)遠(yuǎn)來(lái)看是很有希望的。

性能究竟能提升多少我無(wú)法確定,需要拿自己的業(yè)務(wù)代碼來(lái)進(jìn)行真實(shí)測(cè)試,這樣才能真正清楚 HHVM 能帶來(lái)多少收益,尤其是對(duì)整體性能提升到底有多少,只有拿到這個(gè)數(shù)據(jù)才能做決策。

最后整理一下可能會(huì)遇到的問(wèn)題,有計(jì)劃使用的可以參考:

  • 擴(kuò)展問(wèn)題:如果用到了 PHP 擴(kuò)展,肯定是要重寫(xiě)的,不過(guò) HHVM 擴(kuò)展寫(xiě)起來(lái)比 Zend 要簡(jiǎn)單的多,具體細(xì)節(jié)可以看 wiki 上的例子。
  • HHVM Server 的穩(wěn)定性問(wèn)題:這種多線程的架構(gòu)運(yùn)行一段時(shí)間可能會(huì)出現(xiàn)內(nèi)存泄露問(wèn)題,或者某個(gè)沒(méi)寫(xiě)好的 PHP 直接導(dǎo)致整個(gè)進(jìn)程掛掉,所以需要注意這方面的測(cè)試和容災(zāi)措施。
  • 問(wèn)題修復(fù)困難:HHVM 在出現(xiàn)問(wèn)題時(shí)將比 Zend 難修復(fù),尤其是 JIT 的代碼,只能期望它比較穩(wěn)定了。

P.S. 其實(shí)我只了解基本的虛擬機(jī)知識(shí),也沒(méi)寫(xiě)過(guò)幾行 PHP 代碼,很多東西都是寫(xiě)這篇文章時(shí)臨時(shí)去找資料的,由于時(shí)間倉(cāng)促水平有限,必然會(huì)有不正確的地方,歡迎大家評(píng)論賜教 :)

2014年1月補(bǔ)充:目前 HHVM 在鄙廠的推廣勢(shì)頭很不錯(cuò),推薦大家在2014年嘗試一下,尤其是現(xiàn)在兼容性測(cè)試已經(jīng)達(dá)到98.58%了,修改成本進(jìn)一步減小。

引用

Andrei Alexandrescu on AMA
Keith Adams 在 HN 上的蛛絲馬跡
How Three Guys Rebuilt the Foundation of Facebook PHP on the Metal with HHVM
Making HPHPi Faster
HHVM Optimization Tips
The HipHop Virtual Machine (hhvm) PHP Execution at the Speed of JIT
Julien Verlaguet, Facebook: Analyzing PHP statically
Speeding up PHP-based development with HHVM
Adding an opcode to HHBC

原文: http://wuduoyi.com/note/hhvm/

本網(wǎng)站聲明
本文內(nèi)容由網(wǎng)友自願(yuàn)投稿,版權(quán)歸原作者所有。本站不承擔(dān)相應(yīng)的法律責(zé)任。如發(fā)現(xiàn)涉嫌抄襲或侵權(quán)的內(nèi)容,請(qǐng)聯(lián)絡(luò)admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費(fèi)脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅(qū)動(dòng)的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費(fèi)的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費(fèi)的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強(qiáng)大的PHP整合開(kāi)發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺(jué)化網(wǎng)頁(yè)開(kāi)發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級(jí)程式碼編輯軟體(SublimeText3)

熱門(mén)話題

如何在PHP中獲取當(dāng)前的會(huì)話ID? 如何在PHP中獲取當(dāng)前的會(huì)話ID? Jul 13, 2025 am 03:02 AM

在PHP中獲取當(dāng)前會(huì)話ID的方法是使用session_id()函數(shù),但必須先調(diào)用session_start()才能成功獲取。 1.調(diào)用session_start()啟動(dòng)會(huì)話;2.使用session_id()讀取會(huì)話ID,輸出類(lèi)似abc123def456ghi789的字符串;3.若返回為空,檢查是否遺漏session_start()、用戶是否首次訪問(wèn)或會(huì)話是否被銷(xiāo)毀;4.會(huì)話ID可用於日誌記錄、安全驗(yàn)證和跨請(qǐng)求通信,但需注意安全性。確保正確開(kāi)啟會(huì)話後即可順利獲取ID。

php從字符串獲取子字符串 php從字符串獲取子字符串 Jul 13, 2025 am 02:59 AM

要從PHP字符串中提取子字符串,可使用substr()函數(shù),其語(yǔ)法為substr(string$string,int$start,?int$length=null),若未指定長(zhǎng)度則截取至末尾;處理多字節(jié)字符如中文時(shí)應(yīng)使用mb_substr()函數(shù)以避免亂碼;若需根據(jù)特定分隔符截取字符串,可使用explode()或結(jié)合strpos()與substr()實(shí)現(xiàn),例如提取文件名擴(kuò)展名或域名。

您如何執(zhí)行PHP代碼的單元測(cè)試? 您如何執(zhí)行PHP代碼的單元測(cè)試? Jul 13, 2025 am 02:54 AM

UnittestinginPHPinvolvesverifyingindividualcodeunitslikefunctionsormethodstocatchbugsearlyandensurereliablerefactoring.1)SetupPHPUnitviaComposer,createatestdirectory,andconfigureautoloadandphpunit.xml.2)Writetestcasesfollowingthearrange-act-assertpat

如何將字符串分為PHP中的數(shù)組 如何將字符串分為PHP中的數(shù)組 Jul 13, 2025 am 02:59 AM

在PHP中,最常用的方法是使用explode()函數(shù)將字符串拆分為數(shù)組。該函數(shù)通過(guò)指定的分隔符將字符串分割成多個(gè)部分並返回?cái)?shù)組,語(yǔ)法為explode(separator,string,limit),其中separator為分隔符,string為原字符串,limit為可選參數(shù)控制最大分割數(shù)量。例如$str="apple,banana,orange";$arr=explode(",",$str);結(jié)果為["apple","bana

JavaScript數(shù)據(jù)類(lèi)型:原始與參考 JavaScript數(shù)據(jù)類(lèi)型:原始與參考 Jul 13, 2025 am 02:43 AM

JavaScript的數(shù)據(jù)類(lèi)型分為原始類(lèi)型和引用類(lèi)型。原始類(lèi)型包括string、number、boolean、null、undefined和symbol,其值不可變且賦值時(shí)復(fù)制副本,因此互不影響;引用類(lèi)型如對(duì)象、數(shù)組和函數(shù)存儲(chǔ)的是內(nèi)存地址,指向同一對(duì)象的變量會(huì)相互影響。判斷類(lèi)型可用typeof和instanceof,但需注意typeofnull的歷史問(wèn)題。理解這兩類(lèi)差異有助於編寫(xiě)更穩(wěn)定可靠的代碼。

在C中使用std :: Chrono 在C中使用std :: Chrono Jul 15, 2025 am 01:30 AM

std::chrono在C 中用於處理時(shí)間,包括獲取當(dāng)前時(shí)間、測(cè)量執(zhí)行時(shí)間、操作時(shí)間點(diǎn)與持續(xù)時(shí)間及格式化解析時(shí)間。 1.獲取當(dāng)前時(shí)間使用std::chrono::system_clock::now(),可轉(zhuǎn)換為可讀字符串但係統(tǒng)時(shí)鐘可能不單調(diào);2.測(cè)量執(zhí)行時(shí)間應(yīng)使用std::chrono::steady_clock以確保單調(diào)性,並通過(guò)duration_cast轉(zhuǎn)換為毫秒、秒等單位;3.時(shí)間點(diǎn)(time_point)和持續(xù)時(shí)間(duration)可相互操作,但需注意單位兼容性和時(shí)鐘紀(jì)元(epoch)

如何將會(huì)話變量傳遞給PHP中的另一頁(yè)? 如何將會(huì)話變量傳遞給PHP中的另一頁(yè)? Jul 13, 2025 am 02:39 AM

在PHP中,要將一個(gè)會(huì)話變量傳到另一個(gè)頁(yè)面,關(guān)鍵在於正確開(kāi)啟會(huì)話並使用相同的$_SESSION鍵名。 1.每個(gè)頁(yè)面使用session變量前必須調(diào)用session_start(),且放在腳本最前面;2.在第一個(gè)頁(yè)面設(shè)置session變量如$_SESSION['username']='JohnDoe';3.在另一頁(yè)面同樣調(diào)用session_start()後通過(guò)相同鍵名訪問(wèn)變量;4.確保每個(gè)頁(yè)面都調(diào)用session_start()、避免提前輸出內(nèi)容、檢查服務(wù)器上session存儲(chǔ)路徑可寫(xiě);5.使用ses

PHP如何處理環(huán)境變量? PHP如何處理環(huán)境變量? Jul 14, 2025 am 03:01 AM

toAccessenvironmentVariablesInphp,useGetenv()或$ _envsuperglobal.1.getEnv('var_name')retievesSpecificvariable.2。 $ _ en v ['var_name'] accessesvariablesifvariables_orderInphp.iniincludes“ e” .setVariablesViaCliWithvar = vualitephpscript.php,inapach

See all articles