Wpływ na wydajność używania instanceof w Javie

314

Pracuję nad aplikacją, a jedno podejście projektowe wymaga bardzo intensywnego użycia instanceofoperatora. Chociaż wiem, że projektowanie OO ogólnie stara się unikać używania instanceof, to inna historia i to pytanie jest wyłącznie związane z wydajnością. Zastanawiałem się, czy ma to wpływ na wydajność? Czy to jest tak szybkie jak ==?

Na przykład mam klasę podstawową z 10 podklasami. W jednej funkcji, która pobiera klasę podstawową, sprawdzam, czy klasa jest instancją podklasy i wykonuję jakąś procedurę.

Jednym z innych sposobów, w jaki myślałem o jego rozwiązaniu, było użycie prymitywu liczb całkowitych typu „id” zamiast tego i użycie maski bitowej do reprezentowania kategorii podklas, a następnie po prostu porównanie maski podklasy „identyfikator typu” z stała maska ​​reprezentująca kategorię.

Czy instanceofJVM jest w jakiś sposób zoptymalizowany, aby był szybszy? Chcę trzymać się Javy, ale wydajność aplikacji ma kluczowe znaczenie. Byłoby fajnie, gdyby ktoś, kto był na tej drodze wcześniej, mógł udzielić porady. Czy za dużo robię, czy koncentruję się na niewłaściwej optymalizacji?

Josh
źródło
81
Myślę jednak, że celem pytania było odłożenie na bok pytania o najlepszą praktykę OO i zbadanie wyników.
Dave L.
3
@Dave L. Normalnie zgodziłbym się, ale OP wspomina, że ​​szuka pewnych ogólnych technik optymalizacji i nie jest pewien, czy jego problem jest związany z „instanceof”. Myślę, że warto przynajmniej wspomnieć o „poprawnym” projekcie, aby mógł profilować oba wybory.
Outlaw Programmer,
51
Ugh ... Dlaczego wszystkie odpowiedzi pomijają sedno pytania i przedstawiają tę samą starą retorykę Knutha dotyczącą optymalizacji? Twoje pytanie dotyczy tego, czy instanceof jest znacznie / zaskakująco wolniejszy niż sprawdzanie obiektu klasy za pomocą ==, i przekonałem się, że tak nie jest.
pyzaty
3
Wydajność instanceof i casting jest całkiem dobra. Opublikowałem trochę czasu w Javie 7 wokół różnych podejść do problemu tutaj: stackoverflow.com/questions/16320014/…
Wheezil

Odpowiedzi:

268

Nowoczesne kompilatory JVM / JIC usunęły wydajność większości tradycyjnych „powolnych” operacji, w tym instancji, obsługi wyjątków, odbicia itp.

Jak napisał Donald Knuth: „Powinniśmy zapomnieć o małej wydajności, powiedzmy w około 97% przypadków: przedwczesna optymalizacja jest źródłem wszelkiego zła”. Wydajność instanceof prawdopodobnie nie będzie problemem, więc nie marnuj czasu na wymyślanie egzotycznych obejść, dopóki nie masz pewności, że to jest problem.

Steve
źródło
13
Modern JVM / JIC ..Czy mógłbyś wspomnieć, z której wersji java te optymalizacje zostały objęte?
Ravisha,
138
Zawsze jest ktoś, kto powołuje się na Knutha, gdy tematem jest wydajność ... Zapominając, że Knuth stwierdził również (w tym samym artykule) „W ustalonych dyscyplinach inżynieryjnych poprawa o 12%, łatwo osiągalna, nigdy nie jest uważana za marginalną i uważam, że ten sam punkt widzenia powinien dominować w inżynierii oprogramowania ”, prawie cała jego praca dotyczyła wydajności algorytmów i pisał algorytmy w asemblerze, aby (między innymi) osiągnąć lepszą wydajność. Meh ...
kgadek
4
Na bok tutaj, ale byłoby try { ObjT o = (ObjT)object } catch (e) { no not one of these }wolniej?
peterk
35
Jeśli „obiekt” jest instancją ObjT, rzutowanie jest nieco szybsze niż wykonanie instancji, ale różnica, którą mój szybki test wykrył, to 10-20 ms w porównaniu z 10 000 000 iteracji. Jeśli „obiekt” nie jest ObjT, wyłapanie wyjątku było ponad 3000x wolniejsze - ponad 31 000 ms w porównaniu do ~ 10 ms dla instancji instof.
Steve
19
taki silny argument bez „odniesienia” jest całkowicie bezużyteczny, ponieważ jest tylko wyrażony.
marcorossi
279

Podejście

Napisałem program porównawczy do oceny różnych wdrożeń:

  1. instanceof wdrożenie (jako odniesienie)
  2. obiekt zorientowany za pomocą klasy abstrakcyjnej i @Overridemetody testowej
  3. za pomocą implementacji własnego typu
  4. getClass() == _.class realizacja

Użyłem jmh do uruchomienia testu porównawczego ze 100 wywołaniami rozgrzewki, 1000 iteracji pod pomiarem i 10 widelcami. Tak więc każda opcja była mierzona 10 000 razy, co zajmuje 12:18:57, aby uruchomić cały test porównawczy na moim MacBooku Pro z macOS 10.12.4 i Javą 1.8. Benchmark mierzy średni czas każdej opcji. Aby uzyskać więcej informacji, zobacz moją implementację na GitHub .

Ze względu na kompletność: istnieje poprzednia wersja tej odpowiedzi i mój test porównawczy .

Wyniki

| Operacja | Czas działania w nanosekundach na operację | W odniesieniu do instanceof |
| ------------ | ------------------------------------ - | ------------------------ |
| INSTANCEOF | 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

W Javie 1.8 instanceofjest najszybszym podejściem, choć getClass()jest bardzo blisko.

Michael Dorner
źródło
58
+0.(9)dla nauki!
16
+ pozostałe 0,1 ode mnie: D
Tobias Reich
14
@TobiasReich Więc mamy +1.0(9). :)
Pavel
9
Nie sądzę, żeby to w ogóle mierzyło coś znaczącego. Kod mierzy użycie System.currentTimeMillis()operacji, która nie jest niczym więcej niż pojedynczym wywołaniem metody, co powinno dawać dużo niskiej precyzji. Zamiast tego użyj frameworku, takiego jak JMH !
Lii,
6
Lub po prostu wykonaj harmonogram całego miliarda połączeń zamiast na połączenie.
LegendLength
74

Właśnie wykonałem prosty test, aby zobaczyć, jak wydajność instanceOf wypada w porównaniu z prostym wywołaniem s.equals () do obiektu łańcuchowego zawierającego tylko jedną literę.

w pętli 10.000.000 instancjaOf dała mi 63-96ms, a ciąg równości dał mi 106-230ms

Użyłem java jvm 6.

Tak więc w moim prostym teście jest szybsze wykonanie instanceOf zamiast porównania ciągu znaków.

użycie Integer .equals () zamiast łańcucha dało mi ten sam wynik, tylko gdy użyłem == i byłem szybszy niż instanceOf o 20ms (w pętli 10.000.000)


źródło
4
Czy możesz tutaj opublikować kod? To byłoby niesamowite!
The Alchemist
7
W jaki sposób instanceOf porównał się z poleceniem funkcji polimorficznej?
Chris,
21
Dlaczego porównujesz instanceof z String.equals ()? Jeśli chcesz sprawdzić typ, musisz obiekt.getClass (). Równa się (SomeType.class)
marsbear,
4
@marsbear equals()nie będzie go wycinać, ponieważ podklasowanie ; trzeba isAssignableFrom().
David Moles
1
@marsbear Racja, ale to nie jest lepszy test tego, o co prosi OP.
David Moles,
20

Elementy, które określą wpływ na wydajność, to:

  1. Liczba możliwych klas, dla których operator instanceof może zwrócić wartość true
  2. Dystrybucja twoich danych - czy większość instancji operacji jest rozwiązana w pierwszej czy drugiej próbie? Będziesz chciał postawić swoje najprawdopodobniej na pierwszym miejscu prawdziwe operacje.
  3. Środowisko wdrażania. Uruchamianie na maszynie wirtualnej Sun Solaris różni się znacznie od maszyny JVM firmy Windows. Solaris będzie domyślnie działał w trybie „serwera”, podczas gdy system Windows będzie działał w trybie klienta. Dzięki optymalizacji JIT w systemie Solaris dostęp do wszystkich metod będzie taki sam.

Stworzyłem znak mikrodruku dla czterech różnych metod wysyłki . Wyniki z Solaris są następujące, przy czym mniejsza liczba jest szybsza:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 
Brianegge
źródło
18

Odpowiadając na twoje ostatnie pytanie: Chyba że profiler powie Ci, że spędzasz absurdalnie dużo czasu w instancji: Tak, jesteś podstępny.

Zanim zaczniesz zastanawiać się nad optymalizacją czegoś, co nigdy nie wymagało optymalizacji: napisz swój algorytm w najbardziej czytelny sposób i uruchom go. Uruchom go, aż kompilator jit dostanie szansę na jego optymalizację. Jeśli masz problemy z tym fragmentem kodu, skorzystaj z narzędzia do profilowania, aby dowiedzieć się, gdzie uzyskać najwięcej i zoptymalizować to.

W czasach wysoce optymalizujących kompilatorów twoje przypuszczenia dotyczące wąskich gardeł prawdopodobnie będą całkowicie błędne.

I w prawdziwym duchu tej odpowiedzi (w którą wierzę całym sercem): Absolutnie nie wiem, jak instancje i == odnoszą się, kiedy kompilator jit ma szansę go zoptymalizować.

Zapomniałem: Nigdy nie mierz pierwszego uruchomienia.

Olaf Kock
źródło
1
Ale oryginalny plakat, o którym wspomniano, miał kluczowe znaczenie dla tej aplikacji, więc optymalizacja na wczesnym etapie nie jest nieuzasadniona. Innymi słowy, nie napisałbyś gry 3d w GWBasic, a na koniec powiedz, ok, zacznijmy optymalizować to, pierwszym krokiem jest przeniesienie jej do c ++.
LegendLength
GWBasic może być świetnym początkiem dla gier 3D, jeśli dostępne są odpowiednie biblioteki. Ale na bok (bo to sztuczny argument): OP nie prosi o całkowite przepisanie jako optymalizację. Chodzi o pojedynczą konstrukcję, w której nawet nie wiemy, czy wpływ jest znaczący (nawet jeśli istnieje lepsza wydajność, aby zrobić to samo w bieżącej wersji kompilatora ). Jestem zdecydowanie za c2.com/cgi/wiki?ProfileBeforeOptimizing i moją odpowiedzią. Wstępna optymalizacja jest źródłem wszelkiego zła! Utrudnia konserwację - a konserwacja jest aspektem, który warto zoptymalizować
Olaf Kock
15

Mam to samo pytanie, ale ponieważ nie znalazłem „wskaźników wydajności” dla przypadku użycia podobnego do mojego, zrobiłem trochę więcej przykładowego kodu. Na moim sprzęcie i Javie 6 i 7 różnica między wystąpieniem instancji i włączeniem iteracji 10mln jest następująca

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

Tak więc instanceof jest naprawdę wolniejszy, szczególnie w przypadku dużej liczby instrukcji if-else-if, jednak różnica będzie znikoma w rzeczywistej aplikacji.

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
źródło
Który wynik to java 6, a która java 7? Czy odwiedziłeś to ponownie pod Javą 8? Co ważniejsze, porównujesz długość instanceofs z tym, co jest niezbędne w instrukcji ints. Myślę, że spodziewalibyśmy się, że przełącznik int będzie się błyskawicznie rozjaśniał.
Azeroth2b
1
Nie pamiętam dokładnie, co się działo 5 lat temu - myślę, że zarówno Java 6, jak i Java 7 miały podobny wynik, dlatego jest tylko jeden wynik (pod warunkiem, że 2 linie dotyczą innej głębokości hierarchii klas) ... i nie , nie próbowałem porównywać z Javą 8. Cały kod testu jest dostarczany - możesz go skopiować / wkleić i sprawdzić w wymaganych środowiskach (uwaga - w tej chwili użyłbym do tego testu JMH).
Xtra Coder
9

instanceof jest naprawdę szybki, biorąc tylko kilka instrukcji procesora.

Najwyraźniej, jeśli klasa Xnie ma załadowanych żadnych podklas (JVM wie), instanceofmożna zoptymalizować jako:

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

Główny koszt to tylko odczyt!

Jeśli Xma załadowane podklasy, potrzeba jeszcze kilku odczytów; prawdopodobnie są w tej samej lokalizacji, więc dodatkowy koszt jest również bardzo niski.

Dobre wieści wszyscy!

niezaprzeczalny
źródło
2
mogą być optymalizowane lub jest zoptymalizowana? źródło?
@vaxquis może jako specyficzny dla JVM impl
RecursiveExceptionException
@itz stycznia westchnienie nie trafiłeś w sedno mojego pytania tutaj: wszyscy wiedzą, że kompilator może zoptymalizować foo- ale tak foonaprawdę jest obecnie zoptymalizowany przez javac / VM Oracle - czy to możliwe, że zrobi to w przyszłości? Zapytałem również odpowiadającego, czy ma on jakieś źródło kopii zapasowej (dokumenty, kod źródłowy, blog deweloperski) dokumentujące, że rzeczywiście można go zoptymalizować lub zoptymalizować ? Bez tego ta odpowiedź jest tylko przypadkowym rozmyślaniem o tym, co kompilator może zrobić.
@vaxquis Nigdy nie wspominałeś o VM Hotspot, ale w takim przypadku nie wiem, czy jest ona „zoptymalizowana”.
RecursiveExceptionException
1
Niedawno przeczytałem, że JIT (JVM 8) zoptymalizuje witrynę wywołań dla 1 lub 2 typów przez bezpośrednie połączenia, ale powraca do vtable, jeśli napotkane zostaną więcej niż dwa typy rzeczywiste. Zatem posiadanie tylko dwóch konkretnych typów przechodzących przez witrynę wywoławczą w czasie wykonywania zapewnia przewagę wydajności.
simon.watts
5

Instancjaof jest bardzo szybka. Sprowadza się do kodu bajtowego, który jest używany do porównania referencji klas. Wypróbuj kilka milionów instancji w pętli i przekonaj się sam.

Apokalipsa
źródło
5

instanceof będzie prawdopodobnie bardziej kosztowny niż zwykły równy w większości rzeczywistych implementacji (to znaczy tych, w których instanceof jest naprawdę potrzebny, i nie można go po prostu rozwiązać, zastępując wspólną metodę, jak każdy podręcznik dla początkujących, a także Demian powyżej sugeruje).

Dlaczego? Ponieważ prawdopodobnie stanie się tak, że masz kilka interfejsów, które zapewniają pewną funkcjonalność (powiedzmy, interfejsy x, yiz) oraz niektóre obiekty do manipulowania, które mogą (lub nie) implementować jeden z tych interfejsów ... ale nie bezpośrednio. Powiedzmy na przykład, że mam:

w rozszerza x

A implementuje w

B rozciąga się na A

C rozszerza B, implementuje y

D rozszerza C, implementuje z

Załóżmy, że przetwarzam instancję D, obiekt d. Obliczenia (d instanceof x) wymagają pobrania d.getClass (), przejścia przez interfejsy, które implementuje, aby dowiedzieć się, czy jeden jest == do x, a jeśli nie, wykonaj to rekurencyjnie dla wszystkich ich przodków ... W naszym przypadku jeśli przeprowadzisz pierwszą eksplorację tego drzewa, uzyskasz co najmniej 8 porównań, zakładając, że y i z niczego nie przedłużają ...

Złożoność drzewa pochodnego w świecie rzeczywistym prawdopodobnie będzie wyższa. W niektórych przypadkach JIT może zoptymalizować większość z nich, jeśli jest w stanie rozwiązać z góry d jako we wszystkich możliwych przypadkach, instancję czegoś, co rozszerza x. Jednak realistycznie przez większość czasu będziesz przechodził przez to drzewo.

Jeśli stanie się to problemem, sugerowałbym zamiast tego użyć mapy modułu obsługi, łącząc konkretną klasę obiektu z zamknięciem, które obsługuje. Usuwa fazę przejścia drzewa na korzyść bezpośredniego mapowania. Uważaj jednak, że jeśli ustawiłeś moduł obsługi dla C.class, mój obiekt d powyżej nie zostanie rozpoznany.

oto moje 2 centy, mam nadzieję, że pomogą ...


źródło
5

instanceof jest bardzo wydajny, więc raczej nie ucierpi Twoja wydajność. Jednak użycie wielu instancji sugeruje problem projektowy.

Jeśli możesz użyć xClass == String.class, jest to szybsze. Uwaga: nie potrzebujesz instanceof do końcowych zajęć.

Peter Lawrey
źródło
1
Przy okazji, co masz na myśli mówiąc „nie potrzebujesz instanceof do końcowych zajęć”?
Pacerier
Klasa końcowa nie może mieć podklas. W tym przypadku x.getClass() == Class.classjest to samo cox instanceof Class
Peter Lawrey
Fajnie, zakładając, że x nie jest zerowe, co wolisz?
Pacerier
Słuszna uwaga. To zależy od tego, czy spodziewam xsię nullprzypuszczam. (Lub cokolwiek jest bardziej zrozumiałe)
Peter Lawrey
Hmm Właśnie zdałem sobie sprawę, że możemy również użyć java.lang.class.isAssignableFrom. Czy wiesz, czy słowo kluczowe instanceof używa wewnętrznych funkcji takich jak te?
Pacerier
4

Zasadniczo powodem, dla którego operator „instanceof” jest niezadowolony w takim przypadku (gdy instanceof sprawdza podklasy tej klasy bazowej), jest to, że powinieneś zrobić, aby przenieść operacje do metody i zastąpić ją dla odpowiedniego podklasy. Na przykład, jeśli masz:

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

Możesz to zastąpić

o.doEverything();

a następnie mieć implementację „doEverything ()” w wywołaniu klasy 1 „doThis ()”, a w wywołaniu klasy 2 „doThat ()” i tak dalej.

Paul Tomblin
źródło
11
Ale czasami nie możesz. Jeśli implementujesz interfejs, w którym bierzesz obiekt, i musisz powiedzieć, który to typ, wówczas instanceof jest naprawdę jedyną opcją. Możesz spróbować rzutowania, ale instanceof jest ogólnie czystszy.
Herms,
4

„instanceof” jest w rzeczywistości operatorem, takim jak + lub - i uważam, że ma własną instrukcję kodu bajtowego JVM. Powinno być dość szybko.

Nie powinienem twierdzić, że jeśli masz przełącznik, w którym testujesz, czy obiekt jest instancją jakiejś podklasy, wtedy twój projekt może wymagać przeróbki. Rozważ przesunięcie zachowania specyficznego dla podklasy do samych podklas.

Outlaw Programmer
źródło
4

Demian i Paul wspominają o dobrym punkcie; jednak umieszczenie kodu do wykonania naprawdę zależy od tego, jak chcesz użyć danych ...

Jestem wielkim fanem małych obiektów danych, których można używać na wiele sposobów. Jeśli zastosujesz metodę nadpisywania (polimorficzną), twoich obiektów można używać tylko „w jedną stronę”.

To tutaj pojawiają się wzory ...

Możesz użyć podwójnej wysyłki (jak we wzorcu gościa), aby poprosić każdy obiekt, aby „zadzwonił do ciebie”, przechodząc sam - to rozwiąże rodzaj obiektu. jednak (ponownie) będziesz potrzebować klasy, która może „robić rzeczy” ze wszystkimi możliwymi podtypami.

Wolę używać wzorca strategii, w którym możesz zarejestrować strategie dla każdego podtypu, który chcesz obsłużyć. Coś takiego jak poniżej. Pamiętaj, że pomaga to tylko w dopasowaniu dokładnych typów, ale ma tę zaletę, że można je rozszerzać - osoby trzecie mogą dodawać własne typy i moduły obsługi. (Jest to dobre w przypadku dynamicznych platform, takich jak OSGi, do których można dodawać nowe pakiety)

Mam nadzieję, że zainspiruje to inne pomysły ...

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
źródło
4

Piszę test wydajności oparty na jmh-java-benchmark-archetype: 2.21. JDK to openjdk, a wersja to 1.8.0_212. Maszyna testowa to Mac Pro. Wynik testu to:

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

Wynik pokazuje, że: getClass jest lepszy niż instanceOf, co jest sprzeczne z innym testem. Nie wiem jednak dlaczego.

Kod testowy znajduje się poniżej:

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
źródło
Gdybym miał spekulować, to, co robi instanceof, jest prawdopodobnie bardziej złożone. Kontrola getClass () == wykona dokładną kontrolę 1: 1, przy czym instanceof sprawdza hierarchię, tj. Instancja kolekcji myHashSet przejdzie, ale myHashSet.getClass () == Collection.class nie. Zasadniczo nie są to równoważne operacje, więc nie dziwię się, że wydajność również jest inna.
AMTerp
3

Trudno powiedzieć, w jaki sposób określona JVM implementuje instancję, ale w większości przypadków Obiekty są porównywalne do struktur, a klasy też, a każda struktura obiektu ma wskaźnik do struktury klasy, której jest instancją. Tak więc instancja dla

if (o instanceof java.lang.String)

może być tak szybki jak następujący kod C.

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

zakładając, że kompilator JIT jest na miejscu i wykonuje dobrą robotę.

Biorąc pod uwagę, że jest to tylko dostęp do wskaźnika, uzyskanie wskaźnika przy pewnym przesunięciu, do którego wskazuje wskaźnik, i porównanie go z innym wskaźnikiem (co jest w zasadzie tym samym, co testowanie przy równych liczbach 32-bitowych), powiedziałbym, że operacja może faktycznie bądź bardzo szybki.

Nie musi to jednak zależeć w dużej mierze od JVM. Jeśli jednak okazałoby się, że jest to wąskie gardło w twoim kodzie, uważam implementację JVM za raczej słabą. Nawet taki, który nie ma kompilatora JIT i tylko interpretuje kod, powinien być w stanie wykonać instancję testu praktycznie w mgnieniu oka.

Mecki
źródło
1
Czy nie musi dowiedzieć się, czy dziedziczy po java.lang.String?
WW.
1
Dlatego powiedziałem, że „może” być tak szybkie. W rzeczywistości wykonuje pętlę, najpierw sprawdzając iAmInstanceOf względem danej klasy, a następnie idzie w górę drzewa dziedziczenia o i powtarzając to sprawdzenie dla każdej nadklasy o (więc może być konieczne uruchomienie tej pętli kilka razy na mecz)
Mecki
3

Wrócę do ciebie na podstawie wystąpienia. Ale sposobem na uniknięcie problemu (lub jego braku) byłoby stworzenie interfejsu nadrzędnego dla wszystkich podklas, na których należy wykonać instanceof. Interfejs będzie super zestawem wszystkich metod w podklasach, dla których należy wykonać instancję sprawdzania. Jeżeli metoda nie ma zastosowania do konkretnej podklasy, wystarczy podać fikcyjną implementację tej metody. Jeśli nie zrozumiałem problemu, w ten sposób udało mi się obejść problem w przeszłości.

Jose Quijada
źródło
2

InstanceOf jest ostrzeżeniem o złym projektowaniu obiektowym.

Obecne maszyny JVM oznaczają, że instanceOf sam w sobie nie stanowi problemu z wydajnością. Jeśli często go używasz, szczególnie w przypadku podstawowych funkcji, prawdopodobnie czas przyjrzeć się projektowi. Wzrost wydajności (i prostoty / łatwości konserwacji) dzięki refaktoryzacji do lepszego projektu znacznie przewyższy wszelkie rzeczywiste cykle procesora spędzone na rzeczywistej instancji wywołaniu .

Aby dać bardzo mały uproszczony przykład programowania.

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

Czy w przypadku złej architektury lepszym wyborem byłoby, gdyby SomeObject był klasą nadrzędną dwóch klas podrzędnych, w których każda klasa podrzędna przesłania metodę (doSomething), aby kod wyglądał następująco:

Someobject.doSomething();
Demian Krige
źródło
61
Jestem tego świadomy. Nie o to prosiłem.
Josh
Nie jestem pewien, czy głosować, czy nie, ponieważ jest to dobry punkt, ale nie odpowiada na zadane pytanie ...
jklp
7
Myślę, że przykład kodu jest bardzo zły: nie możesz rozszerzyć klasy Double, a także nie możesz wyprowadzić Double z innej klasy. Gdybyś użył innych klas na przykład, byłoby dobrze.
Lena Schimmel
6
Również jeśli klasy potomne SomeObject są obiektami wartości, nie chcesz umieszczać w nich logiki. Np. Pie i Pieczeń mogą nie być właściwym miejscem dla logiki putInOven () i putInMouth ().
sk.
samo gotowanie ciasta i pieczenia byłoby jednak niesamowite
binboavetonik
2

W nowoczesnej wersji Java operator instanceof jest szybszy jako proste wywołanie metody. To znaczy:

if(a instanceof AnyObject){
}

jest szybszy, ponieważ:

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

Inną sprawą jest konieczność kaskadowania wielu instancji. Następnie przełącznik, który wywołuje tylko raz getType (), jest szybszy.

Horkruks 7
źródło
1

Jeśli jedynym celem jest szybkość, to użycie stałych int do identyfikacji podklas wydaje się zabijać milisekundy czasu

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;
}

okropny projekt OO, ale jeśli analiza wydajności wskazuje, że to jest wąskie gardło, być może. W moim kodzie kod wysyłki zajmuje 10% całkowitego czasu wykonania, co może przyczynić się do poprawy całkowitej prędkości o 1%.

Salix alba
źródło
0

Powinieneś zmierzyć / profil, jeśli to naprawdę problem z wydajnością w twoim projekcie. Jeśli tak, polecam przeprojektowanie - jeśli to możliwe. Jestem prawie pewien, że nie da się pobić natywnej implementacji platformy (napisanej w C). W takim przypadku należy również rozważyć wielokrotne dziedziczenie.

Powinieneś powiedzieć więcej o problemie, być może możesz skorzystać ze sklepu asocjacyjnego, np. Mapa <Klasa, Obiekt>, jeśli jesteś zainteresowany tylko konkretnymi typami.

Karl
źródło
0

W odniesieniu do uwagi Petera Lawrey'a, że ​​nie potrzebujesz instancji dla klas końcowych i możesz po prostu użyć referencyjnej równości, bądź ostrożny! Chociaż końcowych klas nie można rozszerzyć, nie ma gwarancji, że zostaną załadowane przez ten sam moduł ładujący klasy. Używaj x.getClass () == SomeFinal.class lub jego podobnej postaci, jeśli masz absolutną pewność, że dla tej sekcji kodu jest tylko jeden moduł ładujący klasy.


źródło
4
Jeśli klasa jest ładowana przez inny moduł ładujący klasy, nie sądzę, że instanceof też będzie pasował.
Peter Lawrey
0

Wolę także podejście wyliczeniowe, ale użyłbym abstrakcyjnej klasy bazowej, aby zmusić podklasy do wdrożenia getType()metody.

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;
    }
  }
}
mikrofon
źródło
0

Pomyślałem, że warto podać kontrprzykład na ogólny konsensus na tej stronie, że „instanceof” nie jest wystarczająco drogi, aby się martwić. Odkryłem, że mam trochę kodu w wewnętrznej pętli, co (w jakiejś historycznej próbie optymalizacji)

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

gdzie wywołanie head () na SingleItem zwraca wartość bez zmian. Zastąpienie kodu przez

seq = seq.head();

daje mi przyspieszenie od 269 ms do 169 ms, pomimo faktu, że w pętli dzieje się kilka dość ciężkich rzeczy, takich jak konwersja strun do podwójnych. Możliwe jest oczywiście, że przyspieszenie jest bardziej spowodowane wyeliminowaniem gałęzi warunkowej niż wyeliminowaniem samego operatora instanceof; ale pomyślałem, że warto o tym wspomnieć.

Michael Kay
źródło
Może to być spowodowane samym ifsobą. Jeżeli dystrybucja trues i falses jest blisko nawet, wykonywanie spekulatywne staje się bezużyteczny, co prowadzi do znacznych opóźnień.
Dmytro,
-4

Skupiasz się na złej rzeczy. Różnica między instanceof a jakąkolwiek inną metodą sprawdzania tego samego prawdopodobnie nie byłaby nawet mierzalna. Jeśli wydajność ma kluczowe znaczenie, Java prawdopodobnie jest niewłaściwym językiem. Głównym powodem jest to, że nie możesz kontrolować, kiedy maszyna wirtualna zdecyduje, że chce zbierać śmieci, co może doprowadzić procesor do 100% przez kilka sekund w dużym programie (MagicDraw 10 był do tego świetny). Jeśli nie masz kontroli nad każdym komputerem, na którym ten program będzie działał, nie możesz zagwarantować, na której wersji JVM będzie on włączony, a wiele starszych miało poważne problemy z prędkością. Jeśli jest to mała aplikacja, możesz być w porządku z Javą, ale jeśli ciągle czytasz i odrzucasz dane, to zrobisz to zauważyć, gdy kopnięcia w GC.

tloach
źródło
7
Jest to o wiele mniej prawdziwe w przypadku bardziej nowoczesnych algorytmów czyszczenia pamięci java niż kiedykolwiek wcześniej. Nawet najprostsze algorytmy nie dbają już o to, ile pamięci odrzucisz zaraz po użyciu - dbają tylko o to, ile pozostało w kolekcjach młodego pokolenia.
Bill Michell,
3
Świetnie, z wyjątkiem tego, że korzystam z najnowszej maszyny JVM, a mój komputer nadal indeksuje się po uruchomieniu GC. Na dwurdzeniowym serwerze RAM o pojemności 3 GB. Java nie jest językiem używanym, jeśli wydajność ma znaczenie.
tloach
@David: Nie musisz mieć problemów w czasie rzeczywistym, gdy Twoja aplikacja zniknie na pewien czas. Zabawną, z którą się spotkałem, jest aplikacja java, która łączyła się ze strumieniem TCP, który zmarł, gdy GC działał, ponieważ nie zamknął najpierw strumienia i nie był w stanie poradzić sobie z przeciążeniem ruchu sieciowego, gdy wrócił - natychmiast wejdź w pętlę, w której działa GC, gdy wznawia aplikację, próbuje przepełnić wiązkę danych, co spowodowało, że zabrakło jej pamięci, co uruchomiło GC itp. Java jest świetna do wielu zadań, ale nie do zadań, w których bardzo wymagana jest wysoka wydajność.
tloach 28.10.11
6
@tloach brzmi dla mnie jak zły projekt aplikacji. mówisz o „wydajności”, jakby była jednowymiarowa. Pracowałem z wieloma aplikacjami Java, które były na przykład wydajne w zapewnianiu szybkiej interaktywnej analizy statystycznej i wizualizacji bardzo dużych zestawów danych lub bardzo szybko przetwarzały bardzo duże wolumeny transakcji. „wydajność” to nie tylko jedno, a fakt, że ktoś może napisać aplikację, która źle zarządza pamięcią i pozwala GC na własną rękę, nie oznacza, że ​​coś wymagające „wydajności” powinno być zapisane w czymś innym.
David Moles,