Führt Java Casting Overhead ein? Warum?

103

Gibt es einen Overhead, wenn wir Objekte eines Typs in einen anderen umwandeln? Oder löst der Compiler einfach alles auf und es fallen zur Laufzeit keine Kosten an?

Ist das eine allgemeine Sache oder gibt es verschiedene Fälle?

Angenommen, wir haben ein Array von Object [], wobei jedes Element einen anderen Typ haben könnte. Aber wir wissen immer sicher, dass beispielsweise Element 0 ein Double ist, Element 1 ein String. (Ich weiß, dass dies ein falsches Design ist, aber nehmen wir einfach an, dass ich das tun musste.)

Werden die Typinformationen von Java zur Laufzeit noch gespeichert? Oder alles wird nach der Kompilierung vergessen, und wenn wir (Double) -Elemente [0] ausführen, folgen wir einfach dem Zeiger und interpretieren diese 8 Bytes als Double, was auch immer das ist?

Ich bin sehr unklar, wie Typen in Java gemacht werden. Wenn Sie Empfehlungen zu Büchern oder Artikeln haben, dann auch danke.

Phil
quelle
Die Leistung von Instanz und Casting ist ziemlich gut. Ich habe in Java7 ein Timing zu verschiedenen Ansätzen für das Problem hier veröffentlicht: stackoverflow.com/questions/16320014/…
Wheezil
Diese andere Frage hat sehr gute Antworten stackoverflow.com/questions/16741323/…
user454322

Antworten:

77

Es gibt 2 Arten von Casting:

Implizites Casting, wenn Sie von einem Typ in einen breiteren Typ umwandeln, was automatisch erfolgt und kein Overhead entsteht:

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

Explizites Casting, wenn Sie von einem breiteren zu einem schmaleren Typ wechseln. In diesem Fall müssen Sie das Casting explizit wie folgt verwenden:

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

In diesem zweiten Fall entsteht zur Laufzeit ein Overhead, da die beiden Typen überprüft werden müssen und JVM eine ClassCastException auslösen muss, falls ein Casting nicht möglich ist.

Entnommen aus JavaWorld: Die Kosten für das Casting

Das Gießen wird zum Konvertieren zwischen Typen verwendet - insbesondere zwischen Referenztypen, für die Art des Gießvorgangs, an dem wir hier interessiert sind.

Upcast- Vorgänge (in der Java-Sprachspezifikation auch als Erweiterungskonvertierungen bezeichnet) konvertieren eine Unterklassenreferenz in eine Vorgängerklassenreferenz. Dieser Casting-Vorgang erfolgt normalerweise automatisch, da er immer sicher ist und direkt vom Compiler implementiert werden kann.

Downcast- Operationen (in der Java-Sprachspezifikation auch als Verengungskonvertierungen bezeichnet) konvertieren eine Vorgängerklassenreferenz in eine Unterklassenreferenz. Dieser Casting-Vorgang verursacht einen Ausführungsaufwand, da Java erfordert, dass der Cast zur Laufzeit überprüft wird, um sicherzustellen, dass er gültig ist. Wenn das referenzierte Objekt weder eine Instanz des Zieltyps für die Umwandlung noch eine Unterklasse dieses Typs ist, ist die versuchte Umwandlung nicht zulässig und muss eine java.lang.ClassCastException auslösen.

Alex Ntousias
quelle
100
Dieser JavaWorld-Artikel ist mehr als 10 Jahre alt, daher würde ich alle Aussagen über die Leistung mit einem sehr großen Körnchen feinsten Salzes treffen.
Skaffman
@skaffman, in der Tat würde ich jede Aussage, die es macht (unabhängig von der Leistung von nicht), mit einem Körnchen Salz nehmen.
Pacerier
Wird es der gleiche Fall sein, wenn ich das gegossene Objekt nicht der Referenz zuordne und einfach die Methode dafür aufrufe? wie((String)o).someMethodOfCastedClass()
Parth Vishvajit
2
Jetzt ist der Artikel fast 20 Jahre alt. Und die Antworten sind auch viele Jahre alt. Diese Frage braucht eine moderne Antwort.
Raslanove
Wie wäre es mit primitiven Typen? Ich meine, verursacht zum Beispiel das Casting von int nach short einen ähnlichen Overhead?
Luke1985
44

Für eine vernünftige Implementierung von Java:

Jedes Objekt hat einen Header, der unter anderem einen Zeiger auf den Laufzeittyp enthält (zum Beispiel Doubleoder String, aber es könnte niemals sein CharSequenceoder AbstractList). Angenommen, der Laufzeit-Compiler (im Fall von Sun im Allgemeinen HotSpot) kann den Typ nicht statisch bestimmen, und der generierte Maschinencode muss einige Überprüfungen durchführen.

Zuerst muss der Zeiger auf den Laufzeittyp gelesen werden. Dies ist ohnehin erforderlich, um eine virtuelle Methode in einer ähnlichen Situation aufzurufen.

Für das Casting in einen Klassentyp ist genau bekannt, wie viele Oberklassen bis zu Ihrem Treffer vorhanden sind java.lang.Object, sodass der Typ mit einem konstanten Versatz vom Typzeiger gelesen werden kann (tatsächlich die ersten acht in HotSpot). Dies ist wiederum analog zum Lesen eines Methodenzeigers für eine virtuelle Methode.

Dann muss der Lesewert nur noch mit dem erwarteten statischen Typ der Besetzung verglichen werden. Abhängig von der Befehlssatzarchitektur muss ein anderer Befehl auf einen falschen Zweig verzweigen (oder einen Fehler verursachen). ISAs wie 32-Bit-ARM verfügen über bedingte Anweisungen und können möglicherweise den traurigen Pfad durch den glücklichen Pfad führen.

Schnittstellen sind aufgrund der Mehrfachvererbung der Schnittstelle schwieriger. Im Allgemeinen werden die letzten beiden Casts zu Schnittstellen im Laufzeittyp zwischengespeichert. In den frühen Tagen (vor über einem Jahrzehnt) waren die Schnittstellen etwas langsam, aber das ist nicht mehr relevant.

Hoffentlich können Sie sehen, dass solche Dinge für die Leistung weitgehend irrelevant sind. Ihr Quellcode ist wichtiger. In Bezug auf die Leistung ist der größte Erfolg in Ihrem Szenario wahrscheinlich, dass Cache-Fehler auftreten, wenn Objektzeiger überall verfolgt werden (die Typinformationen sind natürlich häufig).

Tom Hawtin - Tackline
quelle
1
interessant - bedeutet dies auch, dass für Klassen ohne Schnittstelle, wenn ich Superklasse schreibe, sc = (Superklasse) Unterklasse; dass der Compiler (jit dh: Ladezeit) den Offset von Object in jeder der Ober- und Unterklassen in ihren "Class" -Headern "statisch" einfügt und dann über ein einfaches Add + Compare in der Lage ist, Dinge aufzulösen? - das ist schön und schnell :) Für Schnittstellen würde ich nicht schlechter als eine kleine Hashtabelle oder einen kleinen Baum annehmen?
Peterk
@peterk Beim Umwandeln zwischen Klassen ändern sich sowohl die Objektadresse als auch das "vtbl" (Tabelle der Methodenzeiger plus Tabelle der Klassenhierarchie, Schnittstellencache usw.) nicht. Die Besetzung [class] überprüft also den Typ, und wenn er passt, muss nichts anderes passieren.
Tom Hawtin - Tackline
8

Angenommen, wir haben ein Array von Object [], wobei jedes Element einen anderen Typ haben könnte. Aber wir wissen immer sicher, dass beispielsweise Element 0 ein Double ist, Element 1 ein String. (Ich weiß, dass dies ein falsches Design ist, aber nehmen wir einfach an, dass ich das tun musste.)

Der Compiler notiert die Typen der einzelnen Elemente eines Arrays nicht. Es wird lediglich überprüft, ob der Typ jedes Elementausdrucks dem Array-Elementtyp zugewiesen werden kann.

Werden die Typinformationen von Java zur Laufzeit noch gespeichert? Oder alles wird nach der Kompilierung vergessen, und wenn wir (Double) -Elemente [0] ausführen, folgen wir einfach dem Zeiger und interpretieren diese 8 Bytes als Double, was auch immer das ist?

Einige Informationen bleiben zur Laufzeit erhalten, nicht jedoch die statischen Typen der einzelnen Elemente. Sie können dies anhand des Klassendateiformats erkennen.

Es ist theoretisch möglich, dass der JIT-Compiler die "Escape-Analyse" verwendet, um unnötige Typprüfungen in einigen Zuweisungen zu vermeiden. Dies in dem von Ihnen vorgeschlagenen Ausmaß zu tun, würde jedoch die Grenzen einer realistischen Optimierung sprengen. Der Aufwand für die Analyse der Arten einzelner Elemente wäre zu gering.

Außerdem sollten Leute sowieso keinen solchen Anwendungscode schreiben.

Stephen C.
quelle
1
Was ist mit Primitiven? (float) Math.toDegrees(theta)Wird es auch hier einen erheblichen Overhead geben?
SD
2
Für einige primitive Casts gibt es einen Overhead. Ob es wichtig ist, hängt vom Kontext ab.
Stephen C
6

Die Bytecode-Anweisung zum Durchführen des Castings zur Laufzeit wird aufgerufen checkcast. Sie können Java-Code zerlegen, indem Sie javapsehen, welche Anweisungen generiert werden.

Bei Arrays speichert Java zur Laufzeit Typinformationen. Meistens fängt der Compiler Typfehler für Sie ab, aber es gibt Fälle, in denen Sie ArrayStoreExceptionbeim Versuch, ein Objekt in einem Array zu speichern, auf einen stoßen, der Typ stimmt jedoch nicht überein (und der Compiler hat ihn nicht abgefangen). . Die Java-Sprachspezifikation enthält das folgende Beispiel:

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 = cpaist gültig, da ColoredPointes sich um eine Unterklasse von Point handelt, pa[0] = new Point()ist jedoch nicht gültig.

Dies steht im Gegensatz zu generischen Typen, bei denen zur Laufzeit keine Typinformationen gespeichert sind. Der Compiler fügt checkcastbei Bedarf Anweisungen ein.

Dieser Unterschied bei der Typisierung für generische Typen und Arrays macht es häufig ungeeignet, Arrays und generische Typen zu mischen.

JesperE
quelle
2

Theoretisch wird Overhead eingeführt. Moderne JVMs sind jedoch intelligent. Jede Implementierung ist anders, aber es ist nicht unangemessen anzunehmen, dass es eine Implementierung geben könnte, bei der JIT Casting-Checks optimiert hat, um sicherzustellen, dass es niemals zu Konflikten kommt. Welche spezifischen JVMs dies anbieten, konnte ich Ihnen nicht sagen. Ich muss zugeben, dass ich die Besonderheiten der JIT-Optimierung gerne selbst kennen würde, aber diese sind für JVM-Ingenieure von Belang.

Die Moral der Geschichte ist, zuerst verständlichen Code zu schreiben. Wenn Sie eine Verlangsamung feststellen, profilieren Sie Ihr Problem und identifizieren Sie es. Die Chancen stehen gut, dass es nicht am Casting liegt. Opfern Sie niemals sauberen, sicheren Code, um ihn zu optimieren, bis Sie wissen, dass Sie es brauchen.

HesNotTheStig
quelle