修改機(jī)器碼的重要性—修改機(jī)器碼軟件好使嗎

修改機(jī)器碼的重要性—修改機(jī)器碼軟件好使嗎

JIT(Just In Time)技術(shù)是Java虛擬機(jī)中的一項(xiàng)重要技術(shù),其在運(yùn)行時(shí)將字節(jié)碼編譯為機(jī)器碼,以大幅提升程序的執(zhí)行速度。

正是因?yàn)镴VM中使用了JIT技術(shù),才為Java代碼在運(yùn)行時(shí)的性能可能超過C++提供了基礎(chǔ)。

一般情況下,我們所產(chǎn)出的代碼,很大層面上需要保障代碼的可讀性,而這里的可讀性是針對(duì)于編碼人員的,而非針對(duì)于機(jī)器。具備高可讀性的代碼,通常并不意味著其可以高效地被機(jī)器直接執(zhí)行,而通常情況下剛好相反

此處,我們針對(duì)JIT中一些常用的優(yōu)化手段,來(lái)理解為何Java代碼的執(zhí)行效率可以如此之高。

經(jīng)過JIT優(yōu)化的代碼的執(zhí)行效率提升,很大層面上是因?yàn)镴IT對(duì)指令進(jìn)行了重新的排列。指令重排在保證代碼邏輯不變的情況下,對(duì)代碼的執(zhí)行順序進(jìn)行了調(diào)整,從而提升了代碼的執(zhí)行效率。

為了理解指令重排,我們需要首先了解JVM所支持的指令是什么樣子的。

對(duì)于已經(jīng)編譯完成的一個(gè)方法,存在三個(gè)重要的組成部分:

  • 本地變量表:用以保存方法的入?yún)⒓奥暶鞯木植孔兞俊?/li>
  • 操作數(shù)棧:用以存儲(chǔ)運(yùn)行的中間結(jié)果。
  • 指令集:即編譯完成后的代碼,也可能稱為字節(jié)碼。

Java指令在JVM規(guī)范中有詳細(xì)的描述,對(duì)應(yīng)版本的JVM都會(huì)擁有一份JVM規(guī)范的文檔,這些文檔被收錄在Oracle的官網(wǎng)中:

在對(duì)應(yīng)文檔的“The Java Machine Instruction Set”章節(jié)中,有對(duì)各種執(zhí)行的詳細(xì)介紹,此處我們不過多贅述,而是簡(jiǎn)單討論一下指令的行為。

JVM所支持的指令,從行為中可分為四類:

  • 從本地變量表或常量池中取出一個(gè)值,并將其壓入到操作數(shù)棧中。如aload_0,將本地變量表中索引為0的值壓入到操作數(shù)棧中。
  • 從操作數(shù)棧中取出操作數(shù)進(jìn)行計(jì)算,并將操作結(jié)果重新壓入到操作數(shù)棧中。如iadd,從操作數(shù)棧中取出兩個(gè)32位整數(shù),并將其相加得到的和重新壓入到操作數(shù)棧中。
  • 從操作數(shù)棧中取出值并寫入到本地變量表中。如astore_0,從操作數(shù)棧中取出一個(gè)值,并將其寫入到本地變量表中索引為0的位置。
  • 用于控制程序跳轉(zhuǎn)。如if_icmpeq、lookupswitch、tableswitch等。

此處我們著重了解前三類指令,首先看示例代碼:

我們將這段代碼編譯成為class文件,并通過javap命令查看編譯后的結(jié)果。

輸出的結(jié)果如下:

這里我們只關(guān)注最后public int compute(int, int)方法中的指令:

我們可以看到,在源代碼中的兩行代碼,編譯完成后得到了8條指令,這8條指令是完全按照源代碼的意圖進(jìn)行直譯的。

而在實(shí)際執(zhí)行中,JVM會(huì)對(duì)指令進(jìn)行簡(jiǎn)化,簡(jiǎn)化后的指令:

我們可以看到,指令從8條被精簡(jiǎn)到了6條,其中針對(duì)操作數(shù)棧頂?shù)闹档淖x取和寫入(即istore_3和iload_3)被合并,從而減少了不必要的操作。

那么此時(shí),我們就可以理解指令重排的意義。

在編碼過程中,從提高代碼可讀性的角度考慮,我們會(huì)將含義、目的將近的變量放到一起聲明和初始化,并在后續(xù)操作中,按更容易理解的業(yè)務(wù)語(yǔ)義來(lái)對(duì)其進(jìn)行批量操作,但是這個(gè)時(shí)候,可能會(huì)導(dǎo)致很多無(wú)效的讀取和寫入操作。為了合并掉這些操作,JVM在邏輯不變的前提下,對(duì)指令進(jìn)行重排,從而使得更多的指令被合并,減少同一代碼執(zhí)行時(shí)所需的指令數(shù)量。

而指令重排所帶來(lái)的好處是顯而易見的,如果指令的數(shù)量被降低10%,那么性能將是實(shí)打?qū)嵉靥嵘?0%。

逃逸分析是在Java6中引入的新特性,其與標(biāo)量替換共同完成運(yùn)行時(shí)的優(yōu)化。

逃逸分析用來(lái)判斷在一個(gè)方法中所實(shí)例化的對(duì)象,是否在方法外被使用。如果對(duì)象在方法外被使用,則表示這個(gè)對(duì)象發(fā)生了“**逃逸**”,否則視作未發(fā)生“**逃逸**”。而對(duì)于未發(fā)生逃逸的對(duì)象,則可通過棧上分配技術(shù),直接在方法棧中為對(duì)象分配內(nèi)存。進(jìn)而通過**標(biāo)量替換**技術(shù),將變量中的字段打散到方法的本地變量表中,后續(xù)對(duì)于對(duì)象中字段的操作,就直接操作這些本地變量,此時(shí)這個(gè)對(duì)象就不見了,取而代之的是表示其所包含字段的本地變量。

標(biāo)量是不可再被細(xì)分的值,如32位整數(shù)、布爾值、字符串等。標(biāo)量不僅局限于基本數(shù)據(jù)類型。

此優(yōu)化所帶來(lái)的好處有:

  • 因?yàn)椴辉傩枰獙?shí)例化對(duì)象,因此減少了堆內(nèi)存的使用,降低了垃圾回收的壓力,更多的內(nèi)存可隨著方法棧的銷毀而直接被釋放。
  • 鎖消除,因?yàn)閷?duì)象不會(huì)發(fā)生逃逸,因此對(duì)象的作用域僅在方法執(zhí)行過程中,因此其是不會(huì)發(fā)生線程同步的。此時(shí)無(wú)效的對(duì)于同步鎖的操作將被消除掉,提升執(zhí)行效率。
  • 替換為標(biāo)量的值,在方法邏輯執(zhí)行過程中,可以參與到指令重排中,從而進(jìn)一步優(yōu)化性能。

因此,對(duì)于以下代碼:

在進(jìn)行標(biāo)量替換后,其實(shí)際的邏輯將近似地被優(yōu)化為:

以上僅是一個(gè)示意,當(dāng)然,此間還涉及到一些其他的優(yōu)化手段,比如內(nèi)聯(lián)等。

內(nèi)聯(lián)的概念比較容易理解,即是將一個(gè)方法的邏輯直接打平打調(diào)用方的代碼中。例如:

在內(nèi)聯(lián)后即成為:

內(nèi)聯(lián)的好處有很多,例如降低代碼的實(shí)際調(diào)用層次等。但是相比于其直接產(chǎn)生的收益,其間接收益則更大。內(nèi)聯(lián)是將各種優(yōu)化手段有效銜接起來(lái)的重要手段。例如,逃逸分析的重要依據(jù)是對(duì)象是否在方法外被使用,如果我們將一個(gè)對(duì)象傳入到一個(gè)方法中,例如對(duì)其字段進(jìn)行校驗(yàn)等,那么這個(gè)對(duì)象就發(fā)生了逃逸,不能應(yīng)用棧上分配、標(biāo)量替換等優(yōu)化手段,更進(jìn)一步也就無(wú)法更好地進(jìn)行指令重排。

而內(nèi)聯(lián)則有效地解決了這個(gè)問題,在實(shí)際代碼運(yùn)行過程中,內(nèi)聯(lián)無(wú)處不在,通常幾層、十幾層的調(diào)用棧,都會(huì)被內(nèi)聯(lián)到一個(gè)方法中。

那么,什么樣的方法可以被內(nèi)聯(lián)呢?

簡(jiǎn)單來(lái)說(shuō),穩(wěn)定的方法可以被內(nèi)聯(lián)。即當(dāng)一個(gè)方法調(diào)用另一個(gè)方法時(shí),如果另一個(gè)方法的邏輯不會(huì)發(fā)生變化,那么這個(gè)方法就可以被內(nèi)聯(lián)到調(diào)用方的方法中。例如final方法、private方法等。

但是因?yàn)閮?nèi)聯(lián)的優(yōu)化手段實(shí)在過于重要,因此JVM后期對(duì)內(nèi)聯(lián)再次進(jìn)行了增強(qiáng),也就是所說(shuō)的激進(jìn)優(yōu)化。這里的激進(jìn)優(yōu)化主要在于可能發(fā)生變化的方法,如通過接口調(diào)用一個(gè)實(shí)現(xiàn)時(shí)。

一般情況下,當(dāng)我們通過接口調(diào)用一個(gè)方法時(shí),我們并不能確定最終調(diào)用的是接口的哪個(gè)實(shí)現(xiàn)。當(dāng)對(duì)這個(gè)接口方法的調(diào)用成為熱點(diǎn),且目標(biāo)方法不曾發(fā)生改變時(shí),將嘗試對(duì)這個(gè)被調(diào)用的方法進(jìn)行內(nèi)聯(lián),如果后續(xù)調(diào)用的目標(biāo)方法發(fā)生變化,則會(huì)進(jìn)行優(yōu)化回退。優(yōu)化回退的成本相對(duì)是很高的,因?yàn)橐话闱闆r下,代碼將被回退到所有優(yōu)化發(fā)生之前的狀態(tài)。

這里所說(shuō)的激進(jìn)優(yōu)化,與JVM參數(shù)中的AggressiveOpts是不同的,AggressiveOpts參數(shù)的開啟表示將啟用當(dāng)前JVM版本中還不成熟的優(yōu)化手段。

那么,激進(jìn)優(yōu)化的意義何在和?

一般情況下,在編碼過程中,需要考慮到諸如并行開發(fā)、接口分離原則等諸多方面,會(huì)使我們的代碼在設(shè)計(jì)層面被拆分成為不同的組件,而大多情況下,這些用作分離的接口通常只有一個(gè)實(shí)現(xiàn)(默認(rèn)實(shí)現(xiàn)),這就為激進(jìn)優(yōu)化帶來(lái)的底層的邏輯支撐。

JVM中還存在諸多的優(yōu)化手段,如分支消除、反射優(yōu)化等。但是總體而言,**JIT的優(yōu)化主要依據(jù)在于熱點(diǎn)代碼判斷,最重要的手段在于方法內(nèi)聯(lián)**。因此當(dāng)我們進(jìn)行代碼設(shè)計(jì)時(shí),首要應(yīng)考慮代碼的內(nèi)聯(lián)屬性。如果組件的代碼是更容易被內(nèi)聯(lián)的,通常情況下,其所帶來(lái)的效率將會(huì)更高。基于此,可以總結(jié)一些有效的代碼設(shè)計(jì)方法:

  • 多抽取工具方法。工具方法的抽取除了代碼更好的可靠性外,也更便于內(nèi)聯(lián)的發(fā)生,并不會(huì)帶來(lái)額外的調(diào)用棧開銷。
  • 明確擴(kuò)展點(diǎn)。在進(jìn)行類設(shè)計(jì)時(shí),對(duì)于哪些方法是需要多態(tài)的應(yīng)該有明確的規(guī)劃,對(duì)于不需要多態(tài)的方法應(yīng)明確使用final進(jìn)行封閉。
  • 單純的沒有多態(tài)的接口分離是不會(huì)帶來(lái)額外的性能損耗的,因?yàn)檫@些方法最終會(huì)被內(nèi)聯(lián)掉。
熱點(diǎn)圖片

備案號(hào):贛ICP備2022005379號(hào)
華網(wǎng)(http://m.acmerblog.com) 版權(quán)所有未經(jīng)同意不得復(fù)制或鏡像

QQ:51985809郵箱:51985809@qq.com

网曝门精品国产事件在线观看 | 国产一精品一av一免费爽爽| 国产精品自拍电影| 精品久久久久久久国产潘金莲| 午夜精品视频在线| 亚洲AV永久无码精品| 99久久精品国产麻豆| 久久国产午夜精品一区二区三区| 国产在线91精品天天更新| 热re久久精品国产99热| 国产精品一品二区三区的使用体验| 蜜芽国内精品视频在线观看 | 国产91精品久久久久久| 国产精品二区高清在线| 精品国产男人的天堂久久| 国产SUV精品一区二区88| 国产乱人伦偷精品视频免| 久久国产综合精品SWAG蓝导航 | 亚洲乱码精品久久久久..| 国内精品久久久久影院网站| 精品无人区无码乱码大片国产| 精品国产三级a乌鸦在线观看| 国产精品久久久久久久午夜片| 国产精品福利在线观看免费不卡| 亚洲国产精品尤物yw在线| 国产精品美女久久久久AV福利| 久久e热在这里只有国产中文精品99| 精品人妻无码专区中文字幕| 久久精品国产黑森林| 亚洲精品国产精品乱码在线观看| 久久福利青草精品资源站免费| 亚洲国产精品无码成人片久久 | 四虎永久在线精品免费观看地址| 精品视频在线观看一区二区| 国产成人精品高清不卡在线 | 久久频这里精品99香蕉久| 久久久一本精品99久久精品36 | 国产大片51精品免费观看| 一级做a爰黑人又硬又粗免费看51社区国产精品视| 人妻少妇精品久久| 九色精品视频在线观看|