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.
Antworten:
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:
Explizites Casting, wenn Sie von einem breiteren zu einem schmaleren Typ wechseln. In diesem Fall müssen Sie das Casting explizit wie folgt verwenden:
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
quelle
((String)o).someMethodOfCastedClass()
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
Double
oderString
, aber es könnte niemals seinCharSequence
oderAbstractList
). 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).
quelle
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.
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.
quelle
(float) Math.toDegrees(theta)
Wird es auch hier einen erheblichen Overhead geben?Die Bytecode-Anweisung zum Durchführen des Castings zur Laufzeit wird aufgerufen
checkcast
. Sie können Java-Code zerlegen, indem Siejavap
sehen, 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
ArrayStoreException
beim 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:Point[] pa = cpa
ist gültig, daColoredPoint
es 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
checkcast
bei 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.
quelle
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.
quelle