修改機器碼的重要性—修改機器碼軟件好使嗎

首頁 > 汽車 > 汽車資訊 > 正文

修改機器碼的重要性—修改機器碼軟件好使嗎

JIT(Just In Time)技術是Java虛擬機中的一項重要技術,其在運行時將字節碼編譯為機器碼,以大幅提升程序的執行速度。

正是因為JVM中使用了JIT技術,才為Java代碼在運行時的性能可能超過C++提供了基礎。

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

此處,我們針對JIT中一些常用的優化手段,來理解為何Java代碼的執行效率可以如此之高。

經過JIT優化的代碼的執行效率提升,很大層面上是因為JIT對指令進行了重新的排列。指令重排在保證代碼邏輯不變的情況下,對代碼的執行順序進行了調整,從而提升了代碼的執行效率。

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

對于已經編譯完成的一個方法,存在三個重要的組成部分:

  • 本地變量表:用以保存方法的入參及聲明的局部變量。
  • 操作數棧:用以存儲運行的中間結果。
  • 指令集:即編譯完成后的代碼,也可能稱為字節碼。

Java指令在JVM規范中有詳細的描述,對應版本的JVM都會擁有一份JVM規范的文檔,這些文檔被收錄在Oracle的官網中:

在對應文檔的“The Java Machine Instruction Set”章節中,有對各種執行的詳細介紹,此處我們不過多贅述,而是簡單討論一下指令的行為。

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

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

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

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

輸出的結果如下:

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

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

而在實際執行中,JVM會對指令進行簡化,簡化后的指令:

我們可以看到,指令從8條被精簡到了6條,其中針對操作數棧頂的值的讀取和寫入(即istore_3和iload_3)被合并,從而減少了不必要的操作。

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

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

而指令重排所帶來的好處是顯而易見的,如果指令的數量被降低10%,那么性能將是實打實地提升10%。

逃逸分析是在Java6中引入的新特性,其與標量替換共同完成運行時的優化。

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

標量是不可再被細分的值,如32位整數、布爾值、字符串等。標量不僅局限于基本數據類型。

此優化所帶來的好處有:

  • 因為不再需要實例化對象,因此減少了堆內存的使用,降低了垃圾回收的壓力,更多的內存可隨著方法棧的銷毀而直接被釋放。
  • 鎖消除,因為對象不會發生逃逸,因此對象的作用域僅在方法執行過程中,因此其是不會發生線程同步的。此時無效的對于同步鎖的操作將被消除掉,提升執行效率。
  • 替換為標量的值,在方法邏輯執行過程中,可以參與到指令重排中,從而進一步優化性能。

因此,對于以下代碼:

在進行標量替換后,其實際的邏輯將近似地被優化為:

以上僅是一個示意,當然,此間還涉及到一些其他的優化手段,比如內聯等。

內聯的概念比較容易理解,即是將一個方法的邏輯直接打平打調用方的代碼中。例如:

在內聯后即成為:

內聯的好處有很多,例如降低代碼的實際調用層次等。但是相比于其直接產生的收益,其間接收益則更大。內聯是將各種優化手段有效銜接起來的重要手段。例如,逃逸分析的重要依據是對象是否在方法外被使用,如果我們將一個對象傳入到一個方法中,例如對其字段進行校驗等,那么這個對象就發生了逃逸,不能應用棧上分配、標量替換等優化手段,更進一步也就無法更好地進行指令重排。

而內聯則有效地解決了這個問題,在實際代碼運行過程中,內聯無處不在,通常幾層、十幾層的調用棧,都會被內聯到一個方法中。

那么,什么樣的方法可以被內聯呢?

簡單來說,穩定的方法可以被內聯。即當一個方法調用另一個方法時,如果另一個方法的邏輯不會發生變化,那么這個方法就可以被內聯到調用方的方法中。例如final方法、private方法等。

但是因為內聯的優化手段實在過于重要,因此JVM后期對內聯再次進行了增強,也就是所說的激進優化。這里的激進優化主要在于可能發生變化的方法,如通過接口調用一個實現時。

一般情況下,當我們通過接口調用一個方法時,我們并不能確定最終調用的是接口的哪個實現。當對這個接口方法的調用成為熱點,且目標方法不曾發生改變時,將嘗試對這個被調用的方法進行內聯,如果后續調用的目標方法發生變化,則會進行優化回退。優化回退的成本相對是很高的,因為一般情況下,代碼將被回退到所有優化發生之前的狀態。

這里所說的激進優化,與JVM參數中的AggressiveOpts是不同的,AggressiveOpts參數的開啟表示將啟用當前JVM版本中還不成熟的優化手段。

那么,激進優化的意義何在和?

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

JVM中還存在諸多的優化手段,如分支消除、反射優化等。但是總體而言,**JIT的優化主要依據在于熱點代碼判斷,最重要的手段在于方法內聯**。因此當我們進行代碼設計時,首要應考慮代碼的內聯屬性。如果組件的代碼是更容易被內聯的,通常情況下,其所帶來的效率將會更高。基于此,可以總結一些有效的代碼設計方法:

  • 多抽取工具方法。工具方法的抽取除了代碼更好的可靠性外,也更便于內聯的發生,并不會帶來額外的調用棧開銷。
  • 明確擴展點。在進行類設計時,對于哪些方法是需要多態的應該有明確的規劃,對于不需要多態的方法應明確使用final進行封閉。
  • 單純的沒有多態的接口分離是不會帶來額外的性能損耗的,因為這些方法最終會被內聯掉。

備案號:贛ICP備2022005379號
華網(http://www.668528.com) 版權所有未經同意不得復制或鏡像

QQ:51985809郵箱:51985809@qq.com

主站蜘蛛池模板: 一区二区三区欧美在线| 亚洲国产91在线| 美女黄网站人色视频免费国产| 国产精品亚洲а∨天堂2021| a级毛片免费全部播放| 无码一区二区三区亚洲人妻| 久久综合九色综合欧美狠狠| 欧美成年黄网站色视频| 人体内射精一区二区三区| 精品视频中文字幕| 国产乱子伦在线观看不卡| 精品国产无限资源免费观看| 国产美女在线观看| a毛片a毛片a视频| 年轻的嫂子在线线观免费观看| 久久久久久一区国产精品 | 污视频免费看网站| 免费特级黄毛片| 老公和他朋友一块上我可以吗| 国产午夜福利片| 国产精品乳摇在线播放| 国产精品反差婊在线观看 | 50岁老女人的毛片免费观看| 女人扒开屁股桶爽30分钟| 东京道一本热中文字幕| 无码专区国产精品视频| 久久免费精品一区二区| 最近中文国语字幕在线播放 | 黄大色黄美女精品大毛片| 国产电影麻豆入口| 120秒男女动态视频免费| 在线免费观看国产| japanese国产在线看| 小蝌蚪视频在线免费观看| 中文字幕三级电影| 教师mm的s肉全文阅读| 久久久久久久久久久久福利| 日本最新免费二区| 国产一级一国产一级毛片| 黄色a三级免费看| 国产成人精品日本亚洲|