哪些因素影響Java調(diào)用的性能

原地址?http://www.codeceo.com/article/java-performance-inspect.html

當(dāng)時(shí)發(fā)生了什么榨咐?

這得從一個(gè)小故事說(shuō)起望蜡。我在一個(gè)Java核心庫(kù)的郵件列表中提交了一個(gè)修改——重寫了一些本是final 的方法。一石激起千層浪宴倍,這一改動(dòng)引發(fā)了幾番討論宇色。而其中一個(gè)討論的話題是:調(diào)用一個(gè)去除final?標(biāo)記的方法,將導(dǎo)致哪種程度的性能下降(performance regression)施禾。

我不能確定這一改變是否會(huì)導(dǎo)致性能下降脚线,但當(dāng)我決定將此暫時(shí)擱置一邊,試著尋找在這個(gè)討論里是否有人公布過(guò)任何相關(guān)的完整基準(zhǔn)測(cè)試(sanebenchmarks)時(shí)弥搞,結(jié)果空手而歸邮绿。我不能肯定地說(shuō)有關(guān)的基準(zhǔn)測(cè)試是不存在的,或者說(shuō)其他人沒(méi)做過(guò)這方面的探討攀例。但我能肯定的是船逮,在這里,連任何公開(kāi)的代碼評(píng)審都沒(méi)有粤铭。唉挖胃,看來(lái)是時(shí)候?qū)懸粋€(gè)基準(zhǔn)測(cè)試了。

基準(zhǔn)測(cè)試的方法論

我決定選用一個(gè)相當(dāng)不錯(cuò)的框架 —— JMH 來(lái)構(gòu)建基準(zhǔn)測(cè)試。如果你質(zhì)疑它測(cè)試的準(zhǔn)確性酱鸭,那么建議你看下對(duì)這個(gè)框架作者(Aleksey Shipilev)的訪談吗垮,或者閱讀一下由Nitsan Wakart撰寫的一篇彰顯此框架風(fēng)采的博文

現(xiàn)在凹髓,我想知道哪些因素影響了Java方法調(diào)用的性能烁登。所以我決定以不同方式調(diào)用方法,并測(cè)算它們的性能開(kāi)銷蔚舀。以單一變量為前提來(lái)構(gòu)造一套基準(zhǔn)測(cè)試饵沧,我便能逐個(gè)排除或確定,哪些因素或哪種組合會(huì)影響到方法調(diào)用的性能赌躺。

內(nèi)聯(lián)

讓我們把這些方法調(diào)用點(diǎn)壓扁

方法調(diào)用的有無(wú)狼牺,是一個(gè)影響程度既是最高又是最低的因素——對(duì)于編譯器來(lái)說(shuō),徹底優(yōu)化方法調(diào)用所帶來(lái)的開(kāi)銷并非不可能寿谴,有兩種方法可以實(shí)現(xiàn)這樣的需求:直接內(nèi)聯(lián)該方法本身和使用內(nèi)聯(lián)緩存(inline cache)锁右。千萬(wàn)別被引入的這些術(shù)語(yǔ)給嚇倒——它們都是通俗易懂的。現(xiàn)在我們假設(shè)有一個(gè)叫Foo的類讶泰,該類定義了一個(gè)叫bar的方法:

classFoo{voidbar(){ ... }}

我們以如下的方式調(diào)用bar方法:

Foo foo =newFoo();foo.bar();

這里有一個(gè)重要的知識(shí)點(diǎn):實(shí)際調(diào)用bar?的位置,即foo.bar()拂到,稱為調(diào)用點(diǎn)(callsite)痪署。當(dāng)我們說(shuō)一個(gè)方法“被內(nèi)聯(lián)”,意指方法體被插入到了調(diào)用點(diǎn)的位置上兄旬,以代替方法調(diào)用狼犯。對(duì)于那些由許多短小的方法所構(gòu)成的程序——我稱之為被適當(dāng)分解的程序——內(nèi)聯(lián)可以有效地提升性能。這是因?yàn)榻Y(jié)束以后可以發(fā)現(xiàn)领铐,程序并沒(méi)有把所有時(shí)間用在方法調(diào)用上悯森,實(shí)際上程序并沒(méi)有工作!我們?cè)贘MH中可以借由CompilerControl?注釋控制一個(gè)方法是否被內(nèi)聯(lián)绪撵。關(guān)于內(nèi)聯(lián)緩存的概念瓢姻,我稍后再來(lái)說(shuō)明。

層次結(jié)構(gòu)深度與重寫子類方法

是因?yàn)楦改缸尯⒆勇聛?lái)了嗎音诈?

如果我們移除一個(gè)方法的final?關(guān)鍵字幻碱,便意味著我們能夠重寫它。所以這是另一個(gè)在進(jìn)行測(cè)試我們需要考慮的情況细溅。我會(huì)選擇在同一層次結(jié)構(gòu)中不同層次的子類里調(diào)用一些方法褥傍,并且在這些方法里有一些是會(huì)被不同層次的子類重寫的。這樣的測(cè)試能讓我們確定或排除深的層次結(jié)構(gòu)是否影響到重寫所帶來(lái)的性能開(kāi)銷喇聊。

多態(tài)性

動(dòng)物世界:多態(tài)是如何表現(xiàn)的

先前我提到調(diào)用點(diǎn)這一概念時(shí)恍风,我偷偷地回避了一個(gè)相當(dāng)重要的問(wèn)題——因?yàn)樵谧宇愔锌梢灾貙懸粋€(gè)非final?方法,這使得調(diào)用點(diǎn)可以調(diào)用不同的方法。現(xiàn)假設(shè)我傳入一個(gè) Foo 的實(shí)例或一個(gè)重寫了bar?子類—— Baz的實(shí)例朋贬,編譯器如何得知要調(diào)用哪一個(gè)bar?方法呢凯楔?在默認(rèn)情況下,方法將在Java中被虛擬化(可重寫)兄世。對(duì)于任一調(diào)用點(diǎn)啼辣,編譯器需要在一個(gè)稱為虛擬表(vtable)的表中尋找與其對(duì)應(yīng)的方法。這是個(gè)非常耗時(shí)的過(guò)程御滩,所以鸥拧,能進(jìn)行優(yōu)化的編譯器,總是會(huì)試圖減少這種查詢帶來(lái)的開(kāi)銷削解。一種方法就是先前提到的內(nèi)聯(lián)富弦,這的確是個(gè)良策,但前提是編譯器能證明在給定的調(diào)用點(diǎn)上調(diào)用的方法唯一氛驮。而這樣的調(diào)用點(diǎn)我們稱為單態(tài)(monomorphic)調(diào)用點(diǎn)腕柜。

不幸的是,進(jìn)行這種分析需要耗費(fèi)大量時(shí)間矫废。所以在實(shí)際過(guò)程中盏缤,確定一個(gè)調(diào)用點(diǎn)是否單態(tài)是個(gè)不太可取的方法。對(duì)此蓖扑,JIT編譯器傾向于使用一種替代方法:列出哪些類可以在此調(diào)用點(diǎn)被調(diào)用唉铜,接著根據(jù)之前的N個(gè)相同的調(diào)用猜測(cè)此調(diào)用點(diǎn)是否是單態(tài)的。以假定某個(gè)調(diào)用點(diǎn)永遠(yuǎn)為單態(tài)律杠,來(lái)進(jìn)行投機(jī)性質(zhì)的優(yōu)化往往是可取的行為潭流。因?yàn)檫@樣的優(yōu)化往往都是正確的,但也因它無(wú)法確保永遠(yuǎn)正確柜去,編譯器需要在方法調(diào)用之前注入一個(gè)用于檢查方法類型的防護(hù)機(jī)制灰嫉。

除了單態(tài)的調(diào)用點(diǎn)以外,還有兩種調(diào)用點(diǎn)我們希望對(duì)其進(jìn)行優(yōu)化嗓奢。一種稱為雙態(tài)(bimorphic)調(diào)用點(diǎn)讼撒,在該點(diǎn)上有兩個(gè)候選方法。對(duì)此你依然可以實(shí)現(xiàn)內(nèi)聯(lián)——借助防護(hù)代碼蔓罚,讓其檢測(cè)應(yīng)調(diào)用哪一個(gè)方法椿肩,并引導(dǎo)程序跳轉(zhuǎn)至內(nèi)聯(lián)在調(diào)用點(diǎn)的兩個(gè)方法體中真正對(duì)應(yīng)的那一個(gè)。這樣的方式還是比查看所有虛擬表的方式要快得多豺谈。但在某些情況下郑象,我們得利用內(nèi)聯(lián)緩存來(lái)進(jìn)行優(yōu)化。內(nèi)聯(lián)緩存需要借助一張?zhí)囟ǖ奶D(zhuǎn)表(?jump table)茬末,這種表類似于對(duì)虛擬表查找做的一份緩存厂榛。hotsopt JIT編譯器支持雙態(tài)內(nèi)聯(lián)緩存盖矫,并定義那些擁有三個(gè)及三個(gè)以上候選方法的調(diào)用點(diǎn)為超多狀態(tài)(megamorphic)調(diào)用點(diǎn)。

這就使得我在基準(zhǔn)測(cè)試與探究當(dāng)中击奶,需要額外地把調(diào)用情況劃分為三類:?jiǎn)螒B(tài)辈双、雙態(tài)、超多狀態(tài)柜砾。

結(jié)果

讓我們把結(jié)果分類組織湃望,以便研究細(xì)節(jié)。我已經(jīng)提供了統(tǒng)計(jì)產(chǎn)生的原始數(shù)據(jù)痰驱。但我們的興趣點(diǎn)不應(yīng)放在性能測(cè)試結(jié)果的具體數(shù)值上证芭,而應(yīng)是不同類型的方法調(diào)用的性能開(kāi)銷之間的比率以及各自的錯(cuò)誤率是否夠低。如果最快與最慢的結(jié)果之間比率為6.26担映,則說(shuō)明這是一個(gè)顯著性差異废士。由于測(cè)試時(shí)使用的是空方法(詳見(jiàn)源代碼),所以在實(shí)際應(yīng)用中蝇完,這樣的差異會(huì)更大官硝。

你可以在github上查看此次基準(zhǔn)測(cè)試的源代碼。為了避免產(chǎn)生困惑短蜕,待會(huì)所有的結(jié)果將分塊顯示氢架。最后顯示的多態(tài)的基準(zhǔn)測(cè)試是在PolymorphicBenchmark?類中進(jìn)行,其它的則在JavaFinalBenchmark?類中朋魔。

簡(jiǎn)單調(diào)用點(diǎn)

最先看到的的一組結(jié)果达箍,是比較調(diào)用一個(gè) virtual 方法、一個(gè)final?方法和一個(gè)擁有很深的層級(jí)結(jié)構(gòu)铺厨,同時(shí)被所有子類重寫的方法所帶來(lái)的開(kāi)銷。注意硬纤,調(diào)用這些方法的時(shí)候我們都強(qiáng)制編譯器不要內(nèi)聯(lián)它們解滓。我們可以看到:三者在時(shí)間花費(fèi)上相差甚微,并且各自的誤差率都小到可以忽略筝家。對(duì)此我們可以斷定洼裤,僅添加一個(gè)final?關(guān)鍵字并不會(huì)大幅度提升調(diào)用性能,重寫一個(gè)方法也不見(jiàn)得會(huì)帶來(lái)什么影響溪王。

內(nèi)聯(lián)簡(jiǎn)單調(diào)用

現(xiàn)在腮鞍,我們?cè)陂_(kāi)啟內(nèi)聯(lián)的情況下再來(lái)一次相同的測(cè)試。由結(jié)果可見(jiàn)莹菱,final?方法和 virtual 方法的時(shí)間花費(fèi)依舊相近移国,并比在沒(méi)有內(nèi)聯(lián)的情況下快了4倍,我將此歸功于內(nèi)聯(lián)優(yōu)化道伟。相比而言迹缀,被所有子類重寫的方法的結(jié)果可就沒(méi)那么好看了使碾。我推測(cè)這是由于此方法有多個(gè)子類實(shí)現(xiàn),使得編譯器必須插入一個(gè)類型保護(hù)祝懂。有關(guān)的細(xì)節(jié)我們將在研究多態(tài)性的結(jié)果時(shí)進(jìn)行闡述票摇。

類層次結(jié)構(gòu)的影響

哇噢——這兒有好幾個(gè)的方法!方法名稱的編號(hào)(1~4)代表該方法調(diào)用的層次砚蓬。因此矢门,parentMethod4 表示我們調(diào)用的方法位于class的上面第四級(jí)。(譯注:在源代碼中該方法位于頂層的父類)灰蛙。由此結(jié)果我們能斷定祟剔,結(jié)構(gòu)層次的深度對(duì)性能開(kāi)銷沒(méi)有影響。在開(kāi)啟內(nèi)聯(lián)的實(shí)例中缕允,結(jié)論也是一樣峡扩。這個(gè)測(cè)試中,被內(nèi)聯(lián)的方法的性能與inlinableAlwaysOverriddenMethod?相當(dāng)障本,但稍遜于?inlinableVirtualInvoke教届。我依舊認(rèn)為這與使用了類型保護(hù)有關(guān)。事實(shí)上JIT編譯器能剖析所有候選方法驾霜,從而只內(nèi)聯(lián)對(duì)應(yīng)的那一個(gè)案训,但這并不證明它總會(huì)這么干。

類的層級(jí)結(jié)構(gòu)對(duì)final方法的影響

該測(cè)試的結(jié)論與第一個(gè)測(cè)試一樣 ——final?關(guān)鍵字不會(huì)產(chǎn)生任何影響粪糙。我本以為該測(cè)試將證明inlinableParentFinalMethod4?以無(wú)類型保護(hù)的方式進(jìn)行內(nèi)聯(lián)强霎,但結(jié)果表明事實(shí)并非如此。

多態(tài)性

最后蓉冈,我們來(lái)看涉及多態(tài)分派(polymorphic

dispatch)的測(cè)試結(jié)果城舞。單態(tài)調(diào)用的性能開(kāi)銷與之前virtual方法相近。但對(duì)于雙態(tài)與超多狀態(tài)調(diào)用寞酿,由于需要在一張較大的虛擬表上面進(jìn)行查找家夺,所以需要更多的時(shí)間。而一旦我們開(kāi)啟內(nèi)聯(lián)支持伐弹,類型分析(type

profiling

)將會(huì)在單態(tài)或雙態(tài)的調(diào)用點(diǎn)啟用拉馋,使得在這些調(diào)用點(diǎn)上的方法調(diào)用的開(kāi)銷減少。但與層級(jí)結(jié)構(gòu)的實(shí)例一樣惨好,這只會(huì)減少少量的時(shí)間煌茴。相比而言,超多狀態(tài)的實(shí)例則依舊耗時(shí)較長(zhǎng)日川。記住蔓腐,我并沒(méi)有說(shuō)在這個(gè)測(cè)試中hotspot禁用了內(nèi)聯(lián),它只是沒(méi)有實(shí)現(xiàn)多態(tài)調(diào)用點(diǎn)的多態(tài)內(nèi)聯(lián)緩存逗鸣。

我們從中學(xué)到了什么合住?

我認(rèn)為绰精,需要我們引起注意的是,很多人沒(méi)有認(rèn)識(shí)到不同方式的方法調(diào)用所花費(fèi)的時(shí)間是不一樣的透葛。即便有些人發(fā)現(xiàn)了這種問(wèn)題笨使,但他們不去證明是否真的如此。作為第一個(gè)吃螃蟹的人僚害,我列出了各種壞的假設(shè)硫椰,因此我希望這份研究能夠幫助到大家。以下是我很樂(lè)于與大家分享的一些結(jié)論:

最快與最短的方法調(diào)用的類型之間存在巨大的性能差別萨蚕。

在實(shí)際應(yīng)用中靶草,添加或刪除final關(guān)鍵字并不會(huì)真正影響性能。但如果除此以外岳遥,你還在層級(jí)結(jié)構(gòu)上進(jìn)行某些操作奕翔,那這些行為則可能導(dǎo)致性能下降。

更深的類的層次結(jié)構(gòu)并不會(huì)真正影響到調(diào)用的性能浩蓉。

單態(tài)調(diào)用比雙態(tài)調(diào)用更快派继。

雙態(tài)調(diào)用比超多狀態(tài)調(diào)用更快。

我們?cè)谀軌蜻M(jìn)行剖析(profile-ably)捻艳,但是不能進(jìn)行查驗(yàn)的單態(tài)調(diào)用點(diǎn)中看到類型保護(hù)驾窟,這種保護(hù)會(huì)使得這些調(diào)用點(diǎn)的調(diào)用性能低于那些能夠進(jìn)行查驗(yàn)的單態(tài)調(diào)用點(diǎn)。

我想說(shuō)的是认轨,對(duì)我而言绅络,類型保護(hù)帶來(lái)的性能開(kāi)銷是一個(gè)“重大發(fā)現(xiàn)”。這是一個(gè)我之前很少提及嘁字,并且總是當(dāng)做無(wú)關(guān)事物忽視掉的因素恩急。

注意事項(xiàng)與進(jìn)一步工作

本文不能囊括這個(gè)話題的全部?jī)?nèi)容。因?yàn)椋?/p>

這篇博文所關(guān)注的影響到方法調(diào)用的性能的因素纪蜒,只與類型有關(guān)假栓。所以,有一個(gè)因素我并未提及:方法的長(zhǎng)短或者說(shuō)調(diào)用棧的深度——如果方法太長(zhǎng)霍掺,那么它將不會(huì)被內(nèi)聯(lián),為此你必須承受方法調(diào)用所帶來(lái)的開(kāi)銷拌蜘。另外杆烁,為了使代碼具有易讀性,你也應(yīng)當(dāng)把方法寫得短小一些简卧。

在本次測(cè)試的所有我并沒(méi)有嘗試引入接口兔魂。如果你對(duì)此有興趣的話,這里有一篇有關(guān)接口調(diào)用的性能的研究Mechanical Sympathy举娩。

還有一個(gè)因素被我完全忽視了析校,那就是方法內(nèi)聯(lián)的優(yōu)化方式在不同編譯器上的效果差異构罗。當(dāng)編譯器是僅關(guān)注某個(gè)方法(內(nèi)部過(guò)程優(yōu)化)時(shí),它們需要足夠地信息才能有效優(yōu)化智玻。內(nèi)聯(lián)的限制可以有效地減少其它優(yōu)化所需要關(guān)注的范圍遂唧。

試著站在匯編語(yǔ)言的層面進(jìn)行解釋的話,會(huì)涉及更多的細(xì)節(jié)內(nèi)容吊奢。

或許以上內(nèi)容已經(jīng)超出了本文的范疇盖彭,需要另寫博文進(jìn)行討論。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末页滚,一起剝皮案震驚了整個(gè)濱河市召边,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌裹驰,老刑警劉巖隧熙,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異幻林,居然都是意外死亡贞盯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門滋将,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)邻悬,“玉大人,你說(shuō)我怎么就攤上這事随闽「阜幔” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵掘宪,是天一觀的道長(zhǎng)蛾扇。 經(jīng)常有香客問(wèn)我,道長(zhǎng)魏滚,這世上最難降的妖魔是什么镀首? 我笑而不...
    開(kāi)封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮鼠次,結(jié)果婚禮上更哄,老公的妹妹穿的比我還像新娘。我一直安慰自己腥寇,他們只是感情好成翩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著赦役,像睡著了一般麻敌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掂摔,一...
    開(kāi)封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天术羔,我揣著相機(jī)與錄音赢赊,去河邊找鬼。 笑死级历,一個(gè)胖子當(dāng)著我的面吹牛释移,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鱼喉,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼秀鞭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了扛禽?” 一聲冷哼從身側(cè)響起锋边,我...
    開(kāi)封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎编曼,沒(méi)想到半個(gè)月后豆巨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掐场,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年往扔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熊户。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡萍膛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嚷堡,到底是詐尸還是另有隱情蝗罗,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布蝌戒,位于F島的核電站串塑,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏北苟。R本人自食惡果不足惜桩匪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望友鼻。 院中可真熱鬧傻昙,春花似錦、人聲如沸彩扔。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)借杰。三九已至,卻和暖如春进泼,著一層夾襖步出監(jiān)牢的瞬間蔗衡,已是汗流浹背纤虽。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绞惦,地道東北人逼纸。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像济蝉,于是被迫代替她去往敵國(guó)和親杰刽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348