簡體   English   中英

Java構建會引入開銷嗎?為什么?

[英]Does Java casting introduce overhead? Why?

當我們將一種類型的對象轉換為另一種類型時會有任何開銷嗎? 或者編譯器只解析所有內容並且在運行時沒有成本?

這是一般事情,還是有不同的情況?

例如,假設我們有一個Object []數組,其中每個元素可能有不同的類型。 但我們總是知道,例如,元素0是一個Double,元素1是一個String。 (我知道這是一個錯誤的設計,但我們假設我必須這樣做。)

Java的類型信息是否仍然在運行時保留? 或者編譯后一切都被遺忘了,如果我們做(Double)元素[0],我們只需跟隨指針並將這8個字節解釋為double,不管是什么?

我不太清楚Java中的類型是如何完成的。 如果您對書籍或文章有任何建議,那么也要感謝。

鑄造有兩種類型:

從類型轉換為更寬類型時的隱式轉換,這是自動完成的,並且沒有開銷:

String s = "Cast";
Object o = s; // implicit casting

顯式鑄造,當你從更寬的類型轉向更窄的類型。 對於這種情況,您必須明確使用類似的:

Object o = someObject;
String s = (String) o; // explicit casting

在第二種情況下,運行時會產生開銷,因為必須檢查這兩種類型,並且如果轉換不可行,JVM必須拋出ClassCastException。

摘自JavaWorld:鑄造成本

Casting用於在類型之間進行轉換 - 特別是在引用類型之間,對於我們感興趣的轉換操作類型。

上傳操作(在Java語言規范中也稱為擴展轉換)將子類引用轉換為祖先類引用。 這種轉換操作通常是自動的,因為它總是安全的並且可以由編譯器直接實現。

向下轉換操作(在Java語言規范中也稱為縮小轉換)將祖先類引用轉換為子類引用。 此轉換操作會產生執行開銷,因為Java要求在運行時檢查轉換以確保它是有效的。 如果引用的對象不是轉換的目標類型或該類型的子類的實例,則不允許嘗試轉換,並且必須拋出java.lang.ClassCastException。

對於Java的合理實現:

每個對象都有一個標題,其中包含一個指向運行時類型的指針(例如DoubleString ,但它永遠不能是CharSequenceAbstractList )。 假設運行時編譯器(在Sun的情況下通常是HotSpot)無法靜態地確定類型,則需要由生成的機器代碼執行某些檢查。

首先,需要讀取指向運行時類型的指針。 無論如何,這對於在類似情況下調用虛擬方法是必要的。

為了轉換為類類型,在確定java.lang.Object之前確切知道有多少個超類,因此可以在類型指針的常量偏移處讀取類型(實際上是HotSpot中的前八個)。 這類似於讀取虛擬方法的方法指針。

然后讀取值只需要與演員的預期靜態類型進行比較。 根據指令集架構,另一條指令需要在不正確的分支上分支(或故障)。 諸如32位ARM之類的ISA具有條件指令,並且可能能夠讓悲傷路徑通過快樂路徑。

由於接口的多重繼承,接口更加困難。 通常,接口的最后兩個強制類型轉換為運行時類型。 在很早的時候(十多年前),接口有點慢,但這已經不再適用了。

希望您可以看到這種事情與性能無關。 您的源代碼更重要。 在性能方面,你的場景中最大的打擊可能是在整個地方追逐對象指針的緩存未命中(類型信息當然是常見的)。

例如,假設我們有一個Object []數組,其中每個元素可能有不同的類型。 但我們總是知道,例如,元素0是一個Double,元素1是一個String。 (我知道這是一個錯誤的設計,但我們假設我必須這樣做。)

編譯器不會記錄數組的各個元素的類型。 它只是檢查每個元素表達式的類型是否可分配給數組元素類型。

Java的類型信息是否仍然在運行時保留? 或者編譯后一切都被遺忘了,如果我們做(Double)元素[0],我們只需跟隨指針並將這8個字節解釋為double,不管是什么?

某些信息在運行時保留,但不是單個元素的靜態類型。 您可以通過查看類文件格式來判斷這一點。

理論上,JIT編譯器可以使用“轉義分析”來消除某些賦值中不必要的類型檢查。 但是,按照您建議的程度執行此操作將超出實際優化的范圍。 分析單個元素類型的收益太小。

此外,人們不應該編寫類似的應用程序代碼。

用於在運行時執行轉換的字節代碼指令稱為checkcast 您可以使用javap反匯編Java代碼以查看生成的指令。

對於數組,Java在運行時保留類型信息。 大多數情況下,編譯器會為您捕獲類型錯誤,但是在嘗試將對象存儲在數組中時會遇到ArrayStoreException ,但類型不匹配(並且編譯器沒有捕獲它) )。 Java語言規范給出了以下示例:

class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
    public static void main(String[] args) {
        ColoredPoint[] cpa = new ColoredPoint[10];
        Point[] pa = cpa;
        System.out.println(pa[1] == null);
        try {
            pa[0] = new Point();
        } catch (ArrayStoreException e) {
            System.out.println(e);
        }
    }
}

Point[] pa = cpa有效,因為ColoredPoint是Point的子類,但是pa[0] = new Point()無效。

這與泛型類型相反,泛型類型在運行時沒有保留類型信息。 編譯器在必要時插入checkcast指令。

泛型類型和數組的類型差異使得它通常不適合混合數組和泛型類型。

從理論上講,引入了開銷。 但是,現代JVM很聰明。 每個實現都是不同的,但假設可以存在一個JIT優化離開轉換檢查的實現,它可以保證永遠不會發生沖突,這是不合理的。 至於哪些特定的JVM提供這個,我無法告訴你。 我必須承認我想知道JIT優化的具體細節,但這些都是JVM工程師所擔心的。

故事的寓意是首先編寫可理解的代碼。 如果您遇到速度減慢,請查看並確定問題。 賠率很高,不會因鑄造而產生。 永遠不要犧牲干凈,安全的代碼,以便在您知道自己需要的情況下優化它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM