Die Auswirkungen der Verwendung von instanceof in Java auf die Leistung

314

Ich arbeite an einer Anwendung und ein Entwurfsansatz beinhaltet eine extrem starke Nutzung des instanceofBedieners. 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 instanceofdie 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?

Josh
quelle
81
Ich denke, der Punkt der Frage war jedoch, die Frage der besten OO-Praxis beiseite zu legen und die Leistung zu untersuchen.
Dave L.
3
@ Dave L. Normalerweise würde ich zustimmen, aber das OP erwähnt, dass er nach allgemeinen Optimierungstechniken sucht und nicht sicher ist, ob sein Problem mit 'instanceof' zusammenhängt. Ich denke, es lohnt sich zumindest, das "richtige" Design zu erwähnen, damit er beide Optionen profilieren kann.
Outlaw Programmer
51
Ugh ... Warum verfehlen alle Antworten den Punkt der Frage und liefern dieselbe alte Knuth-Rhetorik über Optimierung? Ihre Frage ist, ob instanceof signifikant / überraschend langsamer ist als das Überprüfen des Klassenobjekts mit ==, und ich habe festgestellt, dass dies nicht der Fall ist.
Gubby
3
Die Leistung von Instanz und Casting ist ziemlich gut. Ich habe in Java7 einige Zeitangaben zu verschiedenen Ansätzen für das Problem hier veröffentlicht: stackoverflow.com/questions/16320014/…
Wheezil

Antworten:

268

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.

Steve
quelle
13
Modernes JVM / JIC. Könnten Sie bitte erwähnen, von welcher Java-Version diese Optimierung abgedeckt wurde?
Ravisha
138
Es gibt immer jemanden, der Knuth zitiert, wenn es um Leistung geht ... Vergessen, dass Knuth auch (im selben Artikel) sagte: "In etablierten Ingenieurdisziplinen wird eine leicht zu erreichende Verbesserung von 12% nie als marginal angesehen, und ich glaube, dass dies der gleiche Standpunkt ist sollte sich in der Softwareentwicklung durchsetzen ", fast seine gesamte Arbeit befasste sich mit der Effizienz von Algorithmen und er schrieb Algorithmen in Assembler, um (unter anderem) eine bessere Leistung zu erzielen. Meh ...
kgadek
4
Ein beiseite hier aber wäre try { ObjT o = (ObjT)object } catch (e) { no not one of these }schneller schneller ??
Peterk
35
Wenn "Objekt" eine Instanz von ObjT ist, ist das Casting etwas schneller als das Ausführen einer Instanz von ObjT, aber der Unterschied, den mein Schnelltest ergab, betrug 10 bis 20 ms über 10.000.000 Iterationen. Wenn "Objekt" jedoch kein Objekt ist, war das Abfangen der Ausnahme über 3000x langsamer - über 31.000 ms gegenüber ~ 10 ms für die Instanz von.
Steve
19
Ein so starkes Argument ohne "Referenz" ist völlig nutzlos, weil es nur eine Meinung ist.
Marcorossi
279

Ansatz

Ich habe ein Benchmark-Programm geschrieben , um verschiedene Implementierungen zu bewerten:

  1. instanceof Implementierung (als Referenz)
  2. Objektorientiert über eine abstrakte Klasse und @Overrideeine Testmethode
  3. Verwenden einer eigenen Typimplementierung
  4. getClass() == _.class Implementierung

Ich 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

| Bedienung | Laufzeit in Nanosekunden pro Operation Relativ zur Instanz von |
| ------------ | ------------------------------------ - | ------------------------ |
| INSTANZ | 39.598 ± 0,022 ns / op | 100,00% |
| GETCLASS | 39.687 ± 0,021 ns / op | 100,22% |
| TYP | 46.295 ± 0,026 ns / op | 116,91% |
| OO | 48.078 ± 0,026 ns / op | 121,42% |

tl; dr

In Java 1.8 instanceofist der schnellste Ansatz, obwohl getClass()sehr nah.

Michael Dorner
quelle
58
+0.(9)für die Wissenschaft!
16
+ die anderen 0.1 von mir: D
Tobias Reich
14
@TobiasReich Also haben wir +1.0(9). :)
Pavel
9
Ich denke, das misst überhaupt nichts Sinnvolles. Der Code misst anhand 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 !
Lii
6
Oder machen Sie einfach das Timing der gesamten Milliarde Anrufe und nicht pro Anruf.
LegendLength
74

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
4
Könnten Sie den Code hier posten? Das wäre super!
Der Alchemist
7
Wie hat instanceOf mit dem Versand polymorpher Funktionen verglichen?
Chris
21
Warum vergleichen Sie instanceof mit einem String.equals ()? Wenn Sie den Typ überprüfen möchten, müssen Sie object.getClass (). Equals (SomeType.class)
Marsbear
4
@ Marsbear equals()wird es nicht schneiden, weil Unterklassen; du brauchst isAssignableFrom().
David Moles
1
@ Marsbear Richtig, aber das ist kein besserer Test dafür, was das OP verlangt hat.
David Moles
20

Die Elemente, die die Auswirkungen auf die Leistung bestimmen, sind:

  1. Die Anzahl der möglichen Klassen, für die die Instanz des Operators true zurückgeben könnte
  2. Die Verteilung Ihrer Daten - werden die meisten Instanzen von Vorgängen im ersten oder zweiten Versuch gelöst? Sie sollten Ihre Wahrscheinlichkeit, dass echte Operationen zurückgegeben werden, an die erste Stelle setzen.
  3. Die Bereitstellungsumgebung. Die Ausführung auf einer Sun Solaris-VM unterscheidet sich erheblich von der Windows-JVM von Sun. Solaris wird standardmäßig im Servermodus ausgeführt, während Windows im Clientmodus ausgeführt wird. Durch die JIT-Optimierungen unter Solaris wird der Zugriff auf alle Methoden gleich.

Ich habe ein Mikrobenchmark für vier verschiedene Versandmethoden erstellt . Die Ergebnisse von Solaris sind wie folgt, wobei die kleinere Anzahl schneller ist:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 
Brianegge
quelle
18

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.

Olaf Kock
quelle
1
Die Leistung des Originalplakats war jedoch für diese Anwendung von entscheidender Bedeutung. Daher ist es nicht unangemessen, in dieser Situation frühzeitig zu optimieren. Mit anderen Worten, Sie würden kein 3D-Spiel in GWBasic schreiben und dann am Ende sagen, ok, beginnen wir mit der Optimierung. Der erste Schritt besteht darin, es auf c ++ zu portieren.
LegendLength
GWBasic könnte ein guter Start für 3D-Spiele sein, wenn geeignete Bibliotheken verfügbar sind. Abgesehen davon (da es sich um ein künstliches Argument handelt): OP fordert keine vollständige Neufassung als Optimierung. Es handelt sich um ein einzelnes Konstrukt, bei dem wir nicht einmal wissen, ob die Auswirkungen erheblich sind (auch wenn es in der aktuellen Version des Compilers eine bessere Möglichkeit gibt, dasselbe zu tun ). Ich stehe fest hinter c2.com/cgi/wiki?ProfileBeforeOptimizing und meiner Antwort. Vorläufige Optimierung ist die Wurzel allen Übels! Es macht die Wartung schwieriger - und Wartung ist der Aspekt, der es wert ist, optimiert zu werden
Olaf Kock
15

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

for 10 child classes - instanceof: 1200ms vs switch: 470ms
for 5 child classes  - instanceof:  375ms vs switch: 204ms

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.

import java.util.Date;

public class InstanceOfVsEnum {

    public static int c1, c2, c3, c4, c5, c6, c7, c8, c9, cA;

    public static class Handler {
        public enum Type { Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Type9, TypeA }
        protected Handler(Type type) { this.type = type; }
        public final Type type;

        public static void addHandlerInstanceOf(Handler h) {
            if( h instanceof H1) { c1++; }
            else if( h instanceof H2) { c2++; }
            else if( h instanceof H3) { c3++; }
            else if( h instanceof H4) { c4++; }
            else if( h instanceof H5) { c5++; }
            else if( h instanceof H6) { c6++; }
            else if( h instanceof H7) { c7++; }
            else if( h instanceof H8) { c8++; }
            else if( h instanceof H9) { c9++; }
            else if( h instanceof HA) { cA++; }
        }

        public static void addHandlerSwitch(Handler h) {
            switch( h.type ) {
                case Type1: c1++; break;
                case Type2: c2++; break;
                case Type3: c3++; break;
                case Type4: c4++; break;
                case Type5: c5++; break;
                case Type6: c6++; break;
                case Type7: c7++; break;
                case Type8: c8++; break;
                case Type9: c9++; break;
                case TypeA: cA++; break;
            }
        }
    }

    public static class H1 extends Handler { public H1() { super(Type.Type1); } }
    public static class H2 extends Handler { public H2() { super(Type.Type2); } }
    public static class H3 extends Handler { public H3() { super(Type.Type3); } }
    public static class H4 extends Handler { public H4() { super(Type.Type4); } }
    public static class H5 extends Handler { public H5() { super(Type.Type5); } }
    public static class H6 extends Handler { public H6() { super(Type.Type6); } }
    public static class H7 extends Handler { public H7() { super(Type.Type7); } }
    public static class H8 extends Handler { public H8() { super(Type.Type8); } }
    public static class H9 extends Handler { public H9() { super(Type.Type9); } }
    public static class HA extends Handler { public HA() { super(Type.TypeA); } }

    final static int cCycles = 10000000;

    public static void main(String[] args) {
        H1 h1 = new H1();
        H2 h2 = new H2();
        H3 h3 = new H3();
        H4 h4 = new H4();
        H5 h5 = new H5();
        H6 h6 = new H6();
        H7 h7 = new H7();
        H8 h8 = new H8();
        H9 h9 = new H9();
        HA hA = new HA();

        Date dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerInstanceOf(h1);
            Handler.addHandlerInstanceOf(h2);
            Handler.addHandlerInstanceOf(h3);
            Handler.addHandlerInstanceOf(h4);
            Handler.addHandlerInstanceOf(h5);
            Handler.addHandlerInstanceOf(h6);
            Handler.addHandlerInstanceOf(h7);
            Handler.addHandlerInstanceOf(h8);
            Handler.addHandlerInstanceOf(h9);
            Handler.addHandlerInstanceOf(hA);
        }
        System.out.println("Instance of - " + (new Date().getTime() - dtStart.getTime()));

        dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerSwitch(h1);
            Handler.addHandlerSwitch(h2);
            Handler.addHandlerSwitch(h3);
            Handler.addHandlerSwitch(h4);
            Handler.addHandlerSwitch(h5);
            Handler.addHandlerSwitch(h6);
            Handler.addHandlerSwitch(h7);
            Handler.addHandlerSwitch(h8);
            Handler.addHandlerSwitch(h9);
            Handler.addHandlerSwitch(hA);
        }
        System.out.println("Switch of - " + (new Date().getTime() - dtStart.getTime()));
    }
}
Xtra Coder
quelle
Welches Ergebnis war Java 6 und welches war Java 7? Haben Sie dies unter Java 8 erneut besucht? Noch wichtiger ist, dass Sie hier eine Länge von Instanzen von Instanzen mit einer wesentlichen Fallaussage zu Ints vergleichen. Ich denke, wir würden erwarten, dass ein int-Schalter schnell blitzt.
Azeroth2b
1
Ich kann mich nicht genau erinnern, was vor 5 Jahren passiert ist - ich denke, sowohl Java 6 als auch Java 7 hatten ein ähnliches Ergebnis, deshalb wird nur ein Ergebnis bereitgestellt (vorausgesetzt, 2 Zeilen stehen für unterschiedliche Tiefen der Klassenhierarchie) ... und nein Ich habe nicht versucht, mit Java 8 zu vergleichen. Der gesamte Testcode wird bereitgestellt - Sie können ihn kopieren / einfügen und die benötigten Umgebungen überprüfen (Hinweis - heutzutage würde ich dafür den JMH-Test verwenden).
Xtra Coder
9

instanceof ist sehr schnell und benötigt nur wenige CPU-Anweisungen.

Wenn in einer Klasse Xkeine Unterklassen geladen sind (JVM weiß), instanceofkann dies anscheinend wie folgt optimiert werden:

     x instanceof X    
==>  x.getClass()==X.class  
==>  x.classID == constant_X_ID

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!

unwiderlegbar
quelle
2
kann optimiert werden oder ist optimiert? Quelle?
@ Vaxquis kann als sein JVM impl spezifisch
RecursiveExceptionException
@itzJanuary seufzen Sie den Punkt meiner Frage hier verpasst: jeder weiß , dass Compiler kann optimieren foo- aber ist footatsä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.
@vaxquis Du hast die Hotspot-VM nie erwähnt, aber in diesem Fall weiß ich nicht, ob sie "optimiert" ist.
RecursiveExceptionException
1
Lesen Sie kürzlich, dass JIT (JVM 8) eine Anrufsite durch Direktanrufe für 1 oder 2 Typen optimiert, jedoch zur vtable zurückkehrt, wenn mehr als zwei tatsächliche Typen auftreten. Es ist also ein Leistungsvorteil, wenn zur Laufzeit nur zwei konkrete Typen eine Call-Site durchlaufen.
simon.watts
5

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.

Apocalisp
quelle
5

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
5

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.

Peter Lawrey
quelle
1
Übrigens, was meinst du mit "brauche keine Instanz für Abschlussklassen"?
Pacerier
Eine letzte Klasse kann keine Unterklassen haben. In diesem Fall x.getClass() == Class.classist das gleiche wiex instanceof Class
Peter Lawrey
Cool, vorausgesetzt x ist nicht null, was würdest du bevorzugen?
Pacerier
Guter Punkt. Es würde davon abhängen , ob ich erwarte , dass xsein nullnehme ich an. (Oder was auch immer klarer ist)
Peter Lawrey
Hmm, ich habe gerade festgestellt, dass wir auch java.lang.class.isAssignableFrom verwenden können. Wissen Sie, ob die Instanz des Schlüsselworts intern Funktionen wie diese verwendet?
Pacerier
4

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:

if (o instanceof Class1)
   doThis();
else if (o instanceof Class2)
   doThat();
//...

Sie können das durch ersetzen

o.doEverything();

und dann die Implementierung von "doEverything ()" in Klasse 1 aufrufen "doThis ()" und in Klasse 2 "doThat ()" aufrufen und so weiter.

Paul Tomblin
quelle
11
Aber manchmal kannst du nicht. Wenn Sie eine Schnittstelle implementieren, über die Sie ein Objekt aufnehmen, und angeben müssen, um welchen Typ es sich handelt, ist instanceof wirklich die einzige Option. Sie könnten versuchen, zu gießen, aber Instanz davon ist im Allgemeinen sauberer.
Herms
4

'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.

Outlaw Programmer
quelle
4

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 ...

package com.javadude.sample;

import java.util.HashMap;
import java.util.Map;

public class StrategyExample {
    static class SomeCommonSuperType {}
    static class SubType1 extends SomeCommonSuperType {}
    static class SubType2 extends SomeCommonSuperType {}
    static class SubType3 extends SomeCommonSuperType {}

    static interface Handler<T extends SomeCommonSuperType> {
        Object handle(T object);
    }

    static class HandlerMap {
        private Map<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>> handlers_ =
            new HashMap<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>>();
        public <T extends SomeCommonSuperType> void add(Class<T> c, Handler<T> handler) {
            handlers_.put(c, handler);
        }
        @SuppressWarnings("unchecked")
        public <T extends SomeCommonSuperType> Object handle(T o) {
            return ((Handler<T>) handlers_.get(o.getClass())).handle(o);
        }
    }

    public static void main(String[] args) {
        HandlerMap handlerMap = new HandlerMap();

        handlerMap.add(SubType1.class, new Handler<SubType1>() {
            @Override public Object handle(SubType1 object) {
                System.out.println("Handling SubType1");
                return null;
            } });
        handlerMap.add(SubType2.class, new Handler<SubType2>() {
            @Override public Object handle(SubType2 object) {
                System.out.println("Handling SubType2");
                return null;
            } });
        handlerMap.add(SubType3.class, new Handler<SubType3>() {
            @Override public Object handle(SubType3 object) {
                System.out.println("Handling SubType3");
                return null;
            } });

        SubType1 subType1 = new SubType1();
        handlerMap.handle(subType1);
        SubType2 subType2 = new SubType2();
        handlerMap.handle(subType2);
        SubType3 subType3 = new SubType3();
        handlerMap.handle(subType3);
    }
}
Scott Stanchfield
quelle
4

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:

Benchmark                Mode  Cnt    Score   Error   Units
MyBenchmark.getClasses  thrpt   30  510.818 ± 4.190  ops/us
MyBenchmark.instanceOf  thrpt   30  503.826 ± 5.546  ops/us

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:

public class MyBenchmark {

public static final Object a = new LinkedHashMap<String, String>();

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean instanceOf() {
    return a instanceof Map;
}

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean getClasses() {
    return a.getClass() == HashMap.class;
}

public static void main(String[] args) throws RunnerException {
    Options opt =
        new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).warmupIterations(20).measurementIterations(30).forks(1).build();
    new Runner(opt).run();
}
}
salexinx
quelle
Wenn ich spekulieren sollte, ist das, was ich tue, wohl komplexer. Eine getClass () == -Prüfung führt eine genaue 1: 1-Prüfung durch, wobei die Instanz der Überprüfungen eine Hierarchie, dh die myHashSet-Instanz der Sammlung, übergeben würde, die myHashSet.getClass () == Collection.class jedoch nicht. Im Wesentlichen handelt es sich nicht um gleichwertige Vorgänge, daher wundert es mich nicht, dass auch die Leistung unterschiedlich ist.
AMTerp
3

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

if (o instanceof java.lang.String)

ist möglicherweise so schnell wie der folgende C-Code

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

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.

Mecki
quelle
1
Muss es nicht herausfinden, ob o von java.lang.String erbt?
WW.
1
Deshalb habe ich gesagt, es könnte genauso schnell gehen. In Wirklichkeit führt es eine Schleife durch, indem es zuerst iAmInstanceOf mit der betreffenden Klasse vergleicht, dann den Vererbungsbaum von o nach oben geht und diese Prüfung für jede Superklasse von o wiederholt (daher muss diese Schleife möglicherweise einige Male ausgeführt werden für ein Match)
Mecki
3

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.

Jose Quijada
quelle
2

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.

if (SomeObject instanceOf Integer) {
  [do something]
}
if (SomeObject instanceOf Double) {
  [do something different]
}

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:

Someobject.doSomething();
Demian Krige
quelle
61
Das ist mir bewusst. Das habe ich nicht gefragt.
Josh
Wir sind
7
Ich denke, das Codebeispiel ist tatsächlich sehr schlecht: Sie können die Klasse Double nicht erweitern und Double auch nicht von einer anderen Klasse ableiten. Wenn Sie für das Beispiel andere Klassen verwendet hätten, wäre dies in Ordnung gewesen.
Lena Schimmel
6
Auch wenn die untergeordneten Klassen von SomeObject Wertobjekte sind, möchten Sie die Logik nicht in sie einfügen. Zum Beispiel sind Kuchen und Braten möglicherweise nicht der richtige Ort für die Logik putInOven () und putInMouth ().
sk.
Selbst kochen Kuchen und Braten wäre aber
super
2

In der modernen Java-Version ist die Instanz des Operators als einfacher Methodenaufruf schneller. Das heisst:

if(a instanceof AnyObject){
}

ist schneller als:

if(a.getType() == XYZ){
}

Eine andere Sache ist, wenn Sie viele Instanzen davon kaskadieren müssen. Dann ist ein Switch, der nur einmal getType () aufruft, schneller.

Horkrux7
quelle
1

Wenn Geschwindigkeit Ihr einziges Ziel ist, scheint die Verwendung von int-Konstanten zur Identifizierung von Unterklassen eine Millisekunde der Zeit zu sparen

static final int ID_A = 0;
static final int ID_B = 1;
abstract class Base {
  final int id;
  Base(int i) { id = i; }
}
class A extends Base {
 A() { super(ID_A); }
}
class B extends Base {
 B() { super(ID_B); }
}
...
Base obj = ...
switch(obj.id) {
case  ID_A: .... break;
case  ID_B: .... break;
}

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.

Salix alba
quelle
0

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.

Karl
quelle
0

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
4
Wenn eine Klasse von einem anderen Klassenladeprogramm geladen wird, denke ich auch nicht, dass die Instanz von übereinstimmt.
Peter Lawrey
0

Ich bevorzuge auch einen Enum-Ansatz, würde aber eine abstrakte Basisklasse verwenden, um die Unterklassen zur Implementierung der getType()Methode zu zwingen .

public abstract class Base
{
  protected enum TYPE
  {
    DERIVED_A, DERIVED_B
  }

  public abstract TYPE getType();

  class DerivedA extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_A;
    }
  }

  class DerivedB extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_B;
    }
  }
}
Mike
quelle
0

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

if (!(seq instanceof SingleItem)) {
  seq = seq.head();
}

Dabei ruft der Aufruf von head () auf einem SingleItem den Wert unverändert zurück. Ersetzen des Codes durch

seq = seq.head();

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.

Michael Kay
quelle
Dies könnte an sich ifselbst liegen. Wenn die Verteilung von trues und falses nahezu gleichmäßig ist, wird die spekulative Ausführung unbrauchbar, was zu erheblichen Verzögerungen führt.
Dmytro
-4

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.

tloach
quelle
7
Dies gilt viel weniger für die moderneren Java-Garbage-Collection-Algorithmen als je zuvor. Selbst die einfachsten Algorithmen kümmern sich nicht mehr darum, wie viel Speicher Sie unmittelbar nach Ihrer Verwendung verwerfen - sie kümmern sich nur darum, wie viel Speicher in Sammlungen der jungen Generation erhalten bleibt.
Bill Michell
3
Großartig, außer dass ich auf der neuesten JVM bin und mein Computer immer noch crawlt, wenn der GC ausgeführt wird. Auf einem Dual-Core-RAM-Server mit 3 GB. Java ist keine Sprache, wenn die Leistung wirklich wichtig ist.
Tloach
@ David: Sie benötigen keine Echtzeit, um Probleme zu haben, wenn Ihre App für einen bestimmten Zeitraum nicht mehr verfügbar ist. Eine lustige App, auf die ich gestoßen bin, ist eine Java-App, die eine Verbindung zu einem TCP-Stream hergestellt hat, der beim Ausführen des GC gestorben ist, weil er den Stream nicht zuerst geschlossen hat und die Überlastung des Netzwerkverkehrs bei seiner Rückkehr nicht bewältigen konnte - dies würde sofort geschehen Gehen Sie in eine Schleife, in der GC ausgeführt wird. Wenn die App wieder aufgenommen wird, versucht sie, eine Reihe von Daten zu verarbeiten, wodurch der Speicher knapp wird, wodurch der GC ausgelöst wird usw. Java eignet sich hervorragend für viele Aufgaben, jedoch nicht für Aufgaben, bei denen dies sehr wichtig ist Eine starke Leistung ist Voraussetzung.
Tloach
6
@tloach klingt für mich nach schlechtem App-Design. Sie sprechen von "Leistung", als ob sie eindimensional wäre. Ich habe mit (und an) vielen Java-Apps gearbeitet, die beispielsweise eine schnelle interaktive statistische Analyse und Visualisierung sehr großer Datenmengen ermöglichten oder sehr große Transaktionsvolumina sehr schnell verarbeiteten. "Leistung" ist nicht nur eine Sache, und die Tatsache, dass jemand eine Anwendung schreiben kann, die den Speicher schlecht verwaltet und GC auf seine eigene Weise erhält, bedeutet nicht, dass etwas, das "Leistung" erfordert, in etwas anderem geschrieben werden sollte.
David Moles