Pracuję nad aplikacją, a jedno podejście projektowe wymaga bardzo intensywnego użycia instanceof
operatora. 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 instanceof
JVM 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?
źródło
Odpowiedzi:
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.
źródło
try { ObjT o = (ObjT)object } catch (e) { no not one of these }
wolniej?Podejście
Napisałem program porównawczy do oceny różnych wdrożeń:
instanceof
wdrożenie (jako odniesienie)@Override
metody testowejgetClass() == _.class
realizacjaUż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
tl; dr
W Javie 1.8
instanceof
jest najszybszym podejściem, choćgetClass()
jest bardzo blisko.źródło
+0.(9)
dla nauki!+1.0(9)
. :)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 !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
equals()
nie będzie go wycinać, ponieważ podklasowanie ; trzebaisAssignableFrom()
.Elementy, które określą wpływ na wydajność, to:
Stworzyłem znak mikrodruku dla czterech różnych metod wysyłki . Wyniki z Solaris są następujące, przy czym mniejsza liczba jest szybsza:
źródło
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.
źródło
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
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.
źródło
instanceof
jest naprawdę szybki, biorąc tylko kilka instrukcji procesora.Najwyraźniej, jeśli klasa
X
nie ma załadowanych żadnych podklas (JVM wie),instanceof
można zoptymalizować jako:Główny koszt to tylko odczyt!
Jeśli
X
ma 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!
źródło
foo
- ale takfoo
naprawdę 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ć.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.
źródło
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
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ęć.
źródło
x.getClass() == Class.class
jest to samo cox instanceof Class
x
sięnull
przypuszczam. (Lub cokolwiek jest bardziej zrozumiałe)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:
Możesz to zastąpić
a następnie mieć implementację „doEverything ()” w wywołaniu klasy 1 „doThis ()”, a w wywołaniu klasy 2 „doThat ()” i tak dalej.
źródło
„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.
źródło
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 ...
źródło
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:
Wynik pokazuje, że: getClass jest lepszy niż instanceOf, co jest sprzeczne z innym testem. Nie wiem jednak dlaczego.
Kod testowy znajduje się poniżej:
źródło
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
może być tak szybki jak następujący kod C.
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.
źródło
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.
źródło
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.
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:
źródło
W nowoczesnej wersji Java operator instanceof jest szybszy jako proste wywołanie metody. To znaczy:
jest szybszy, ponieważ:
Inną sprawą jest konieczność kaskadowania wielu instancji. Następnie przełącznik, który wywołuje tylko raz getType (), jest szybszy.
źródło
Jeśli jedynym celem jest szybkość, to użycie stałych int do identyfikacji podklas wydaje się zabijać milisekundy czasu
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%.
źródło
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.
źródło
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
Wolę także podejście wyliczeniowe, ale użyłbym abstrakcyjnej klasy bazowej, aby zmusić podklasy do wdrożenia
getType()
metody.źródło
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)
gdzie wywołanie head () na SingleItem zwraca wartość bez zmian. Zastąpienie kodu przez
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ć.
źródło
if
sobą. Jeżeli dystrybucjatrue
s ifalse
s jest blisko nawet, wykonywanie spekulatywne staje się bezużyteczny, co prowadzi do znacznych opóźnień.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.
źródło