Ich arbeite an einer Anwendung und ein Entwurfsansatz beinhaltet eine extrem starke Nutzung des instanceof
Bedieners. Obwohl ich weiß, dass OO-Design generell versucht, die Verwendung zu vermeiden instanceof
, ist dies eine andere Geschichte, und diese Frage bezieht sich ausschließlich auf die Leistung. Ich habe mich gefragt, ob es irgendwelche Auswirkungen auf die Leistung gibt. Ist ist genauso schnell wie ==
?
Zum Beispiel habe ich eine Basisklasse mit 10 Unterklassen. In einer einzelnen Funktion, die die Basisklasse verwendet, überprüfe ich, ob die Klasse eine Instanz der Unterklasse ist, und führe eine Routine aus.
Eine der anderen Möglichkeiten, die ich zu lösen dachte, bestand darin, stattdessen ein ganzzahliges Grundelement "Typ-ID" zu verwenden und eine Bitmaske zur Darstellung von Kategorien der Unterklassen zu verwenden und dann einfach einen Bitmaskenvergleich der Unterklassen "Typ-ID" mit a durchzuführen konstante Maske, die die Kategorie darstellt.
Wird instanceof
die JVM irgendwie so optimiert, dass sie schneller ist? Ich möchte mich an Java halten, aber die Leistung der App ist entscheidend. Es wäre cool, wenn jemand, der schon einmal auf diesem Weg war, Ratschläge geben könnte. Wähle ich nicht zu viel oder konzentriere ich mich auf das Falsche, um es zu optimieren?
quelle
Antworten:
Moderne JVM / JIC-Compiler haben die Leistungseinbußen der meisten traditionell "langsamen" Vorgänge beseitigt, einschließlich Instanzen, Ausnahmebehandlung, Reflektion usw.
Wie Donald Knuth schrieb: "Wir sollten kleine Wirkungsgrade vergessen, etwa in 97% der Fälle: Vorzeitige Optimierung ist die Wurzel allen Übels." Die Leistung von instanceof wird wahrscheinlich kein Problem sein. Verschwenden Sie also keine Zeit damit, exotische Problemumgehungen zu finden, bis Sie sicher sind, dass dies das Problem ist.
quelle
try { ObjT o = (ObjT)object } catch (e) { no not one of these }
schneller schneller ??Ansatz
Ich habe ein Benchmark-Programm geschrieben , um verschiedene Implementierungen zu bewerten:
instanceof
Implementierung (als Referenz)@Override
eine TestmethodegetClass() == _.class
ImplementierungIch habe jmh verwendet , um den Benchmark mit 100 Aufwärmaufrufen, 1000 zu messenden Iterationen und mit 10 Gabeln auszuführen. Daher wurde jede Option mit 10 000 Mal gemessen, was 12:18:57 dauert, um den gesamten Benchmark auf meinem MacBook Pro mit macOS 10.12.4 und Java 1.8 auszuführen. Der Benchmark misst die durchschnittliche Zeit jeder Option. Weitere Details finden Sie in meiner Implementierung auf GitHub .
Der Vollständigkeit halber: Es gibt eine frühere Version dieser Antwort und meinen Benchmark .
Ergebnisse
tl; dr
In Java 1.8
instanceof
ist der schnellste Ansatz, obwohlgetClass()
sehr nah.quelle
+0.(9)
für die Wissenschaft!+1.0(9)
. :)System.currentTimeMillis()
einer Operation, die nicht viel mehr als ein einzelner Methodenaufruf ist, was zu geringer Genauigkeit führen sollte. Verwenden Sie stattdessen ein Benchmark-Framework wie JMH !Ich habe gerade einen einfachen Test durchgeführt, um zu sehen, wie die Leistung von instanceOf mit einem einfachen Aufruf von s.equals () für ein Zeichenfolgenobjekt mit nur einem Buchstaben verglichen wird.
In einer 10.000.000-Schleife gab mir die Instanz von 63-96 ms und die Zeichenfolge gleich 106-230 ms
Ich habe Java JVM 6 verwendet.
In meinem einfachen Test ist es also schneller, eine Instanz von statt eines Zeichenfolgenvergleichs durchzuführen.
Die Verwendung von Integers .equals () anstelle von Strings ergab das gleiche Ergebnis, nur wenn ich == i verwendete, war ich um 20 ms schneller als instanceOf (in einer 10.000.000-Schleife).
quelle
equals()
wird es nicht schneiden, weil Unterklassen; du brauchstisAssignableFrom()
.Die Elemente, die die Auswirkungen auf die Leistung bestimmen, sind:
Ich habe ein Mikrobenchmark für vier verschiedene Versandmethoden erstellt . Die Ergebnisse von Solaris sind wie folgt, wobei die kleinere Anzahl schneller ist:
quelle
Beantwortung Ihrer allerletzten Frage: Wenn Ihnen ein Profiler nicht sagt, dass Sie lächerlich viel Zeit in einem Fall verbringen: Ja, Sie wählen nicht.
Bevor Sie sich über die Optimierung von etwas wundern, das nie optimiert werden musste: Schreiben Sie Ihren Algorithmus so gut wie möglich und führen Sie ihn aus. Führen Sie es aus, bis der JIT-Compiler die Möglichkeit hat, es selbst zu optimieren. Wenn Sie dann Probleme mit diesem Code haben, verwenden Sie einen Profiler, um zu erfahren, wo Sie am meisten gewinnen und diesen optimieren können.
In Zeiten hochoptimierender Compiler werden Ihre Vermutungen zu Engpässen wahrscheinlich völlig falsch sein.
Und im wahren Geist dieser Antwort (an die ich von ganzem Herzen glaube): Ich weiß absolut nicht, wie Instanz und == sich beziehen, sobald der JIT-Compiler die Möglichkeit hat, sie zu optimieren.
Ich habe vergessen: Niemals den ersten Lauf messen.
quelle
Ich habe die gleiche Frage, aber da ich keine 'Leistungsmetriken' für einen ähnlichen Anwendungsfall wie meinen gefunden habe, habe ich etwas mehr Beispielcode erstellt. Auf meiner Hardware und Java 6 & 7 ist der Unterschied zwischen Instanz von und Einschalten von 10-mln-Iterationen
Die Instanz von ist also wirklich langsamer, insbesondere bei einer großen Anzahl von if-else-if-Anweisungen, jedoch ist der Unterschied innerhalb der realen Anwendung vernachlässigbar.
quelle
instanceof
ist sehr schnell und benötigt nur wenige CPU-Anweisungen.Wenn in einer Klasse
X
keine Unterklassen geladen sind (JVM weiß),instanceof
kann dies anscheinend wie folgt optimiert werden:Die Hauptkosten sind nur eine Lektüre!
Wenn
X
Unterklassen geladen sind, sind einige weitere Lesevorgänge erforderlich. Sie befinden sich wahrscheinlich am selben Ort, sodass auch die zusätzlichen Kosten sehr gering sind.Gute Neuigkeiten alle zusammen!
quelle
foo
- aber istfoo
tatsächlich derzeit von Oracle javac / VM optimiert - oder ist es nur möglich , dass es , dass in Zukunft tun werde? Außerdem habe ich den Antwortenden gefragt, ob er eine Hintergrundquelle (Dokumente, Quellcode, Entwickler-Blog) hat, die dokumentiert, dass diese tatsächlich optimiert werden kann oder optimiert wird . Ohne sie ist diese Antwort nur einige zufällige Grübelei über das, was Compiler kann möglicherweise tun.Instanz von ist sehr schnell. Es läuft auf einen Bytecode hinaus, der für den Klassenreferenzvergleich verwendet wird. Probieren Sie einige Millionen Instanzen in einer Schleife aus und überzeugen Sie sich selbst.
quelle
instanceof wird wahrscheinlich teurer sein als ein einfaches Equal in den meisten Implementierungen der realen Welt (dh diejenigen, in denen instanceof wirklich benötigt wird, und Sie können es nicht einfach lösen, indem Sie eine gemeinsame Methode überschreiben, wie jedes Lehrbuch für Anfänger ebenso wie Demian oben vorschlagen).
Warum ist das so? Denn was wahrscheinlich passieren wird, ist, dass Sie mehrere Schnittstellen haben, die einige Funktionen (z. B. die Schnittstellen x, y und z) und einige zu manipulierende Objekte bereitstellen, die möglicherweise eine dieser Schnittstellen implementieren (oder nicht) ... aber nicht direkt. Sagen Sie zum Beispiel, ich habe:
w erstreckt sich x
A implementiert w
B erstreckt sich A.
C erweitert B, implementiert y
D erweitert C, implementiert z
Angenommen, ich verarbeite eine Instanz von D, das Objekt d. Das Rechnen (d Instanz von x) erfordert, dass d.getClass () verwendet wird, die implementierten Schnittstellen durchlaufen werden, um zu wissen, ob eine == zu x ist, und wenn nicht, rekursiv für alle ihre Vorfahren erneut ausgeführt wird ... In unserem Fall Wenn Sie diesen Baum zuerst breit untersuchen, erhalten Sie mindestens 8 Vergleiche, vorausgesetzt, y und z erweitern nichts ...
Die Komplexität eines realen Ableitungsbaums ist wahrscheinlich höher. In einigen Fällen kann die JIT das meiste davon wegoptimieren, wenn sie in der Lage ist, im Voraus d als in allen möglichen Fällen eine Instanz von etwas aufzulösen, das x erweitert. Realistisch gesehen werden Sie jedoch die meiste Zeit diese Baumdurchquerung durchlaufen.
Wenn dies zu einem Problem wird, würde ich vorschlagen, stattdessen eine Handler-Map zu verwenden, die die konkrete Klasse des Objekts mit einem Abschluss verknüpft, der die Behandlung übernimmt. Es entfernt die Baumdurchquerungsphase zugunsten einer direkten Zuordnung. Beachten Sie jedoch, dass mein Objekt d oben nicht erkannt wird, wenn Sie einen Handler für C.class festgelegt haben.
Hier sind meine 2 Cent, ich hoffe sie helfen ...
quelle
instanceof ist sehr effizient, sodass Ihre Leistung wahrscheinlich nicht darunter leidet. Die Verwendung vieler Instanzen von deutet jedoch auf ein Designproblem hin.
Wenn Sie xClass == String.class verwenden können, ist dies schneller. Hinweis: Für die Abschlussklassen benötigen Sie keine Instanz.
quelle
x.getClass() == Class.class
ist das gleiche wiex instanceof Class
x
seinnull
nehme ich an. (Oder was auch immer klarer ist)Im Allgemeinen ist der Grund, warum der Operator "instanceof" in einem solchen Fall (in dem die Instanzof nach Unterklassen dieser Basisklasse sucht) verpönt ist, der, dass Sie die Operationen in eine Methode verschieben und für die entsprechende überschreiben sollten Unterklassen. Zum Beispiel, wenn Sie haben:
Sie können das durch ersetzen
und dann die Implementierung von "doEverything ()" in Klasse 1 aufrufen "doThis ()" und in Klasse 2 "doThat ()" aufrufen und so weiter.
quelle
'instanceof' ist eigentlich ein Operator wie + oder - und ich glaube, dass er einen eigenen JVM-Bytecode-Befehl hat. Es sollte schnell genug sein.
Ich sollte nicht sagen, dass wenn Sie einen Schalter haben, an dem Sie testen, ob ein Objekt eine Instanz einer Unterklasse ist, Ihr Design möglicherweise überarbeitet werden muss. Ziehen Sie in Betracht, das unterklassenspezifische Verhalten in die Unterklassen selbst zu verschieben.
quelle
Demian und Paul erwähnen einen guten Punkt; jedoch , die Platzierung des Codes auszuführen hängt wirklich davon ab , wie Sie die Daten verwenden ...
Ich bin ein großer Fan von kleinen Datenobjekten, die auf viele Arten verwendet werden können. Wenn Sie dem Überschreibungsansatz (polymorph) folgen, können Ihre Objekte nur "in eine Richtung" verwendet werden.
Hier kommen Muster ins Spiel ...
Sie können den Doppelversand (wie im Besuchermuster) verwenden, um jedes Objekt aufzufordern, Sie "anzurufen" und sich selbst zu übergeben. Dadurch wird der Objekttyp aufgelöst. jedoch benötigen (wieder) eine Klasse, die mit allen möglichen Untertypen "Sachen machen" kann.
Ich bevorzuge die Verwendung eines Strategiemusters, bei dem Sie Strategien für jeden Subtyp registrieren können, den Sie verarbeiten möchten. So etwas wie das Folgende. Beachten Sie, dass dies nur für exakte Typübereinstimmungen hilfreich ist, aber den Vorteil hat, dass es erweiterbar ist - Drittanbieter können ihre eigenen Typen und Handler hinzufügen. (Dies ist gut für dynamische Frameworks wie OSGi, bei denen neue Bundles hinzugefügt werden können.)
Hoffentlich inspiriert dies einige andere Ideen ...
quelle
Ich schreibe einen Leistungstest basierend auf jmh-java-Benchmark-Archetyp: 2.21. JDK ist openjdk und die Version ist 1.8.0_212. Die Testmaschine ist Mac Pro. Testergebnis ist:
Das Ergebnis zeigt Folgendes: getClass ist besser als instanceOf, was im Gegensatz zu anderen Tests steht. Ich weiß jedoch nicht warum.
Der Testcode ist unten:
quelle
Es ist schwer zu sagen, wie eine bestimmte JVM eine Instanz von implementiert, aber in den meisten Fällen sind Objekte mit Strukturen vergleichbar, und Klassen sind es auch, und jede Objektstruktur hat einen Zeiger auf die Klassenstruktur, von der sie eine Instanz ist. Also eigentlich Instanz von für
ist möglicherweise so schnell wie der folgende C-Code
vorausgesetzt, ein JIT-Compiler ist vorhanden und leistet gute Arbeit.
In Anbetracht dessen, dass dies nur auf einen Zeiger zugreift, einen Zeiger mit einem bestimmten Versatz erhält, auf den der Zeiger zeigt, und diesen mit einem anderen Zeiger vergleicht (was im Grunde dem Testen von 32-Bit-Zahlen entspricht, die gleich sind), würde ich sagen, dass die Operation tatsächlich möglich ist sei sehr schnell.
Es muss jedoch nicht viel von der JVM abhängen. Sollte sich jedoch herausstellen, dass dies der Engpass in Ihrem Code ist, würde ich die JVM-Implementierung als eher schlecht betrachten. Selbst einer, der keinen JIT-Compiler hat und nur Code interpretiert, sollte in praktisch kürzester Zeit eine Testinstanz erstellen können.
quelle
Ich werde mich umgehend bei Ihnen melden. Eine Möglichkeit, Probleme (oder deren Fehlen) insgesamt zu vermeiden, besteht darin, eine übergeordnete Schnittstelle zu allen Unterklassen zu erstellen, für die Sie eine Instanz ausführen müssen. Die Schnittstelle ist eine Supermenge aller Methoden in Unterklassen, für die Sie eine Instanz der Prüfung durchführen müssen. Wenn eine Methode nicht für eine bestimmte Unterklasse gilt, stellen Sie einfach eine Dummy-Implementierung dieser Methode bereit. Wenn ich das Problem nicht falsch verstanden habe, bin ich in der Vergangenheit so um das Problem herumgekommen.
quelle
InstanceOf ist eine Warnung vor schlechtem objektorientiertem Design.
Aktuelle JVMs bedeuten, dass die Instanz von an sich keine großen Leistungsprobleme darstellt. Wenn Sie feststellen, dass Sie es häufig verwenden, insbesondere für Kernfunktionen, ist es wahrscheinlich an der Zeit, sich das Design anzusehen. Die Leistungsgewinne (und die Einfachheit / Wartbarkeit) des Refactorings zu einem besseren Design überwiegen alle tatsächlichen Prozessorzyklen, die für die tatsächliche Instanz des Aufrufs aufgewendet wurden, erheblich .
Um ein sehr kleines vereinfachtes Programmierbeispiel zu geben.
Ist eine schlechte Architektur eine bessere Wahl gewesen, wäre SomeObject die übergeordnete Klasse von zwei untergeordneten Klassen gewesen, wobei jede untergeordnete Klasse eine Methode (doSomething) überschreibt, sodass der Code wie folgt aussehen würde:
quelle
In der modernen Java-Version ist die Instanz des Operators als einfacher Methodenaufruf schneller. Das heisst:
ist schneller als:
Eine andere Sache ist, wenn Sie viele Instanzen davon kaskadieren müssen. Dann ist ein Switch, der nur einmal getType () aufruft, schneller.
quelle
Wenn Geschwindigkeit Ihr einziges Ziel ist, scheint die Verwendung von int-Konstanten zur Identifizierung von Unterklassen eine Millisekunde der Zeit zu sparen
schreckliches OO-Design, aber wenn Ihre Leistungsanalyse zeigt, dass Sie hier möglicherweise einen Engpass haben. In meinem Code nimmt der Versandcode 10% der gesamten Ausführungszeit in Anspruch, was möglicherweise zu einer Verbesserung der Gesamtgeschwindigkeit um 1% beiträgt.
quelle
Sie sollten messen / profilieren, ob es sich wirklich um ein Leistungsproblem in Ihrem Projekt handelt. Wenn ja, würde ich eine Neugestaltung empfehlen - wenn möglich. Ich bin mir ziemlich sicher, dass Sie die native Implementierung der Plattform (geschrieben in C) nicht übertreffen können. In diesem Fall sollten Sie auch die Mehrfachvererbung berücksichtigen.
Sie sollten mehr über das Problem erzählen. Vielleicht können Sie einen assoziativen Speicher verwenden, z. B. eine Karte <Klasse, Objekt>, wenn Sie nur an den konkreten Typen interessiert sind.
quelle
Seien Sie vorsichtig, wenn Sie Peter Lawreys Hinweis beachten, dass Sie für die Abschlussklassen keine Instanz benötigen und nur eine Referenzgleichheit verwenden können! Obwohl die endgültigen Klassen nicht erweitert werden können, kann nicht garantiert werden, dass sie von demselben Klassenladeprogramm geladen werden. Verwenden Sie x.getClass () == SomeFinal.class oder dessen ilk nur, wenn Sie absolut sicher sind, dass für diesen Codeabschnitt nur ein Klassenladeprogramm im Spiel ist.
quelle
Ich bevorzuge auch einen Enum-Ansatz, würde aber eine abstrakte Basisklasse verwenden, um die Unterklassen zur Implementierung der
getType()
Methode zu zwingen .quelle
Ich dachte, es könnte sich lohnen, ein Gegenbeispiel zum allgemeinen Konsens auf dieser Seite einzureichen, dass "instanceof" nicht teuer genug ist, um sich Sorgen zu machen. Ich stellte fest, dass ich Code in einer inneren Schleife hatte, der (in einem historischen Optimierungsversuch) dies tat
Dabei ruft der Aufruf von head () auf einem SingleItem den Wert unverändert zurück. Ersetzen des Codes durch
gibt mir eine Beschleunigung von 269 ms auf 169 ms, obwohl in der Schleife einige ziemlich schwere Dinge passieren, wie die Umwandlung von String in Doppel. Es ist natürlich möglich, dass die Beschleunigung mehr auf das Eliminieren des bedingten Zweigs als auf das Eliminieren der Instanz des Operators selbst zurückzuführen ist. aber ich fand es erwähnenswert.
quelle
if
selbst liegen. Wenn die Verteilung vontrue
s undfalse
s nahezu gleichmäßig ist, wird die spekulative Ausführung unbrauchbar, was zu erheblichen Verzögerungen führt.Sie konzentrieren sich auf das Falsche. Der Unterschied zwischen instanceof und einer anderen Methode zur Überprüfung derselben Sache wäre wahrscheinlich nicht einmal messbar. Wenn die Leistung kritisch ist, ist Java wahrscheinlich die falsche Sprache. Der Hauptgrund ist, dass Sie nicht steuern können, wann die VM entscheidet, dass sie Müll sammeln möchte, wodurch die CPU in einem großen Programm für einige Sekunden auf 100% gebracht werden kann (MagicDraw 10 war dafür großartig). Wenn Sie nicht die Kontrolle über jeden Computer haben, auf dem dieses Programm ausgeführt wird, können Sie nicht garantieren, auf welcher JVM-Version es ausgeführt wird, und viele der älteren hatten große Geschwindigkeitsprobleme. Wenn es sich um eine kleine Anwendung ist können Sie mit Java in Ordnung sein, aber wenn man ständig liest und verwirft Daten dann Sie werden feststellen , wenn die GC Kicks.
quelle