亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

JDK 中的非常規代碼 - 用于未知原因的特定構造

JDK 中的非常規代碼 - 用于未知原因的特定構造

慕萊塢森 2023-06-08 17:27:18
我正在查看 JDK(JDK 12,但它也適用于舊版本)代碼并發現了一些奇怪的結構,我不明白為什么要使用它們。讓我們舉個例子Map.computeIfPresent,因為它很簡單:default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {    Object oldValue;    if ((oldValue = this.get(key)) != null) {        V newValue = remappingFunction.apply(key, oldValue);        if (newValue != null) {            this.put(key, newValue);            return newValue;        } else {            this.remove(key);            return null;        }    } else {        return null;    }}這個結構if ((oldValue = this.get(key)) != null)讓我很吃驚。我知道這是可能的,因為它沒什么特別的,但在正常的生產代碼中我會認為它是一種代碼味道。為什么不直接寫成正常的方式(Object oldValue = this.get(key))?一定是一些離合器優化,這就是我的想法。寫了一個較小的版本來檢查字節碼:int computeIfPresent(int key) {  Integer oldValue;  if ((oldValue = get(key)) != null) {    return oldValue;  } else {    return 2;  }}字節碼輸出:int computeIfPresent(int);  Code:     0: aload_0     1: iload_1     2: invokevirtual #2                  // Method get:(I)Ljava/lang/Integer;     5: dup     6: astore_2     7: ifnull        15    10: aload_2    11: invokevirtual #3                  // Method java/lang/Integer.intValue:()I    14: ireturn    15: iconst_2    16: ireturn具有經典變量初始化的“普通”版本的字節碼:int computeIfPresent(int);  Code:     0: aload_0     1: iload_1     2: invokevirtual #2                  // Method get:(I)Ljava/lang/Integer;     5: astore_2     6: aload_2     7: ifnull        15    10: aload_2    11: invokevirtual #3                  // Method java/lang/Integer.intValue:()I    14: ireturn    15: iconst_2    16: ireturn唯一的區別是dup + astore_2vs。astore_2 + aload_2我什至懷疑第一個“離合器優化”版本更糟,因為dup它被使用并且堆棧無緣無故地更大。也許我的例子太簡單了,優化在更復雜的上下文中擴展了很多。這是一個簡單的例子,絕對不是 JDK 代碼中的一個例子——打開HashMap.java,有很多這樣的片段,有時在同一行有多個:if ((first = tab[i = (n - 1) & hash]) != null)盡管它真的很簡單,但由于這些結構,我不得不停下來想一想這段代碼實際上做了什么。使用這些結構背后的真正原因是什么?我敢肯定這不僅僅是糟糕的代碼。在我看來,代碼質量受到很大影響,因此收益一定是可觀的。或者只是規則leave small optimizations to JIT不適用于 JDK,因為它必須盡可能多地壓縮性能?或者這只是規則的極端initialize variables as late as possible?:)
查看完整描述

1 回答

?
慕哥9229398

TA貢獻1877條經驗 獲得超6個贊

您的問題的答案在于 JVM 規范,特別是您指出的不同之處:指令dup(JVMS §6.5.dup)。從那些文檔:

復制操作數棧頂部的值并將復制的值壓入操作數棧。

查看操作數堆棧文檔(JVMS §2.6.2,重點添加):

少量 Java 虛擬機指令(dup指令 (§dup) 和swap(§swap))作為原始值在運行時數據區域上運行,而不考慮它們的特定類型;這些指令的定義方式使其不能用于修改或分解單個值。這些對操作數堆棧操作的限制是通過類文件驗證(§4.10)強制執行的。

再深入一層,查看類驗證部分(JVMS §4.10,重點添加):

鏈接時驗證增強了運行時解釋器的性能。可以消除在運行時為每條解釋指令驗證約束而必須執行的昂貴檢查。Java 虛擬機可以假定這些檢查已經執行。

這表明這些限制是在鏈接時驗證的,也就是 JVM 加載您的類文件時。所以回答你的問題:

使用這些結構背后的真正原因是什么?

讓我們剖析一下指令在每種情況下的作用:

在第一種情況下(使用說明dup):

  1. invokevirtual將結果存儲在操作數棧的頂部

  2. dup重復所以現在在堆棧頂部有兩個結果副本

  3. astore_2將其存儲到局部變量 #2 中,該變量從操作數堆棧中彈出一個引用

  4. ifnull檢查操作數棧的頂部是否為空,如果是,則轉到指令 15,否則繼續(我們假設它不為空)

  5. aload_2將局部變量#2 推入操作數棧的頂部

  6. invokevirtual在操作數棧的頂部調用一個方法,彈出它,然后壓入結果

  7. ireturn從操作數棧彈出頂部值并返回它

在第二種情況下:

  1. invokevirtual將結果存儲在操作數棧的頂部

  2. astore_2將結果彈出操作數棧并將其存儲在局部變量 #2 中

  3. aload_2將局部變量#2 推入操作數棧的頂部

  4. ifnull檢查操作數棧的頂部是否為空,如果是,則轉到指令 15,否則繼續(我們假設它不為空)

  5. aload_2將局部變量#2 推入操作數棧的頂部

  6. invokevirtual在操作數棧的頂部調用一個方法,彈出它,然后壓入結果

  7. ireturn從操作數棧彈出頂部值并返回它

那么有什么區別呢?第一個調用aload_2一次又一次dup,第二個只調用aload兩次。這里的區別幾乎沒有。如果查看整個操作過程中堆棧的大小,您會發現第一個實現將操作數堆棧增加了一個額外的值(少于 10 個字節,通常為 8 或 4 個字節,具體取決于 64 位或 32 位 JVM ), 但從堆棧內存中加載的局部變量少了一個。第二個使操作數堆棧稍微小一些,但有一個額外的局部變量加載(讀?。簭膬却嬷蝎@取)。

歸根結底,這些優化的影響非常小,除非是在內存極低的應用程序中,例如嵌入式系統。那么對你來說?做可讀的事情。

如有疑問:“過早優化(可能)是萬惡之源?!?除非您知道您的代碼很慢或者可以在運行之前證明它很慢,否則最好編寫可讀的代碼。這幾乎不屬于您應該提前優化的關鍵 3%。


查看完整回答
反對 回復 2023-06-08
  • 1 回答
  • 0 關注
  • 139 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號