2010-01-31 15 views
84

Czy są jakieś koszty ogólne, gdy rzucamy obiekty jednego rodzaju do drugiego? Lub kompilator po prostu rozwiązuje wszystko i nie ma żadnych kosztów w czasie wykonywania?Czy rzutowanie Java wprowadza koszty ogólne? Czemu?

Czy to ogólne, czy są różne przypadki?

Załóżmy na przykład, że mamy tablicę Object [], gdzie każdy element może mieć inny typ. Ale zawsze wiemy na pewno, że, powiedzmy, element 0 jest podwójnym, element 1 jest ciągiem. (Wiem, że to niewłaściwy projekt, ale załóżmy, że musiałem to zrobić.)

Czy informacje o typie Java są nadal przechowywane w czasie wykonywania? Lub wszystko jest zapomniane po kompilacji, a jeśli zrobimy (podwójne) elementy [0], po prostu podążymy za wskaźnikiem i zinterpretujemy te 8 bajtów jako podwójne, cokolwiek to jest?

Nie bardzo wiem, jak typy są wykonywane w Javie. Jeśli masz jakieś zalecenia dotyczące książek lub artykułów, to dziękuję.

+0

Wydajność instanceof i casting jest całkiem dobra. Opublikowalem trochę czasu w Java7 na temat różnych podejść do problemu tutaj: http://stackoverflow.com/questions/16320014/java-optimization-nitpick-is-it-faster-to-cast-something-and-let-it- throw-excep/28858680 # 28858680 – Wheezil

+0

To drugie pytanie ma bardzo dobre odpowiedzi http://stackoverflow.com/questions/16741323/casting-performance-in-different-levels-when-casting-down – user454322

Odpowiedz

63

Istnieją 2 rodzaje rzucania:

niejawny casting, gdy rzucisz od typu do szerszego rodzaju, który odbywa się automatycznie i nie ma nad głową:

String s = "Cast"; 
Object o = s; // implicit casting 

Explicit odlewanie, gdy przechodzisz od szerszego typu do bardziej wąskiego. W tym przypadku, trzeba jawnie użyć odlewania tak:

Object o = someObject; 
String s = (String) o; // explicit casting 

W tym drugim przypadku, nie ma nad głową w czasie wykonywania, ponieważ oba typy muszą być sprawdzane, aw przypadku, że odlew nie jest możliwe, JVM musi rzucić Wyjątek ClassCastException.

Zrobione z JavaWorld: The cost of casting

Casting służy do konwersji między typów - między typami referencyjnymi w szczególności do rodzaju odlewy operację, w którym jesteśmy zainteresowani tutaj.

uskok operacje (zwane również konwersji rozszerzających w specyfikacji języka Java ) przekonwertować odniesienie podklasy do przodka odniesienia klasy. Ta operacja odlewania jest zwykle automatyczna, ponieważ jest zawsze bezpieczna i może być zaimplementowana bezpośrednio przez kompilator.

spuszczonymi operacje (zwane również zwężenie konwersje w specyfikacji języka Java ) przekonwertować odniesienie klasy przodka do podklasy odniesienia. Ta operacja odlewania tworzy narzut na wykonanie, ponieważ Java wymaga, aby obsada była sprawdzana w środowisku wykonawczym , aby upewnić się, że jest poprawna. Jeżeli odwołanie obiekt nie jest wystąpienie obu typu docelowej obsady lub podklasy tego typu, próba obsada jest niedozwolone i musi rzucić java.lang.ClassCastException.

+79

Ten artykuł JavaWorld ma ponad 10 lat stary, więc biorę wszelkie oświadczenia, które on robi o wydajności z bardzo dużym ziarnem swojej najlepszej soli. – skaffman

+0

@skaffman, W rzeczywistości bym wziął dowolne oświadczenie jakie robi (bez względu na wydajność nie) z przymrużeniem oka. – Pacerier

+0

będzie to ten sam przypadek, jeśli nie przypiszesz rzutowanego obiektu do referencji i po prostu wywołasz na nim metodę? jak '((String) o) .someMethodOfCastedClass()' –

7

Na przykład załóżmy, że ma szereg Object [], w której każdy element może mieć różnego rodzaju. Ale zawsze wiemy na pewno, że, powiedzmy, element 0 jest podwójnym, element 1 jest ciągiem. (Wiem, że to niewłaściwy projekt, ale załóżmy, że musiałem to zrobić.)

Kompilator nie odnotowuje typów poszczególnych elementów tablicy. Po prostu sprawdza, czy typ każdego wyrażenia elementu można przypisać do typu elementu tablicy.

Czy informacje o typie Java są nadal przechowywane w czasie wykonywania? Lub wszystko jest zapomniane po kompilacji, a jeśli zrobimy (podwójne) elementy [0], po prostu podążymy za wskaźnikiem i zinterpretujemy te 8 bajtów jako podwójne, cokolwiek to jest?

Niektóre informacje są przechowywane w czasie wykonywania, ale nie statyczne typy poszczególnych elementów. Możesz to powiedzieć, patrząc na format pliku klasy.

Jest teoretycznie możliwe, że kompilator JIT mógłby użyć "analizy ucieczki" w celu wyeliminowania niepotrzebnych kontroli typów w niektórych zadaniach. Jednak robienie tego w takim stopniu, w jakim sugerujesz, byłoby poza realistyczną optymalizacją. Wypłata z analizy rodzajów poszczególnych elementów byłaby zbyt mała.

Poza tym ludzie nie powinni tak pisać kodu aplikacji tak.

+0

Co z prymitywami? '(float) Math.toDegrees (theta)' Czy będzie tu również znaczny spadek? –

+1

Istnieje pewien narzut dla niektórych prymitywnych rzutów. To, czy jest znaczące, zależy od kontekstu. –

5

Instrukcja kodu bajtowego do wykonywania rzutowania w czasie wykonywania nosi nazwę checkcast. Możesz dezasemblować kod Java za pomocą javap, aby zobaczyć, jakie instrukcje są generowane.

W przypadku tablic Java przechowuje informacje o typie w czasie wykonywania. W większości przypadków kompilator wykryje dla ciebie błędy typu, ale są przypadki, w których natrafisz na ArrayStoreException podczas próby przechowywania obiektu w tablicy, ale typ nie pasuje (i kompilator go nie przechwycił). Java language spec podaje następujący przykład:

class Point { int x, y; } 
class ColoredPoint extends Point { int color; } 
class Test { 
    public static void main(String[] args) { 
     ColoredPoint[] cpa = new ColoredPoint[10]; 
     Point[] pa = cpa; 
     System.out.println(pa[1] == null); 
     try { 
      pa[0] = new Point(); 
     } catch (ArrayStoreException e) { 
      System.out.println(e); 
     } 
    } 
} 

Point[] pa = cpa jest ważny od ColoredPoint jest podklasą Point, ale pa[0] = new Point() jest nieprawidłowy.

Jest to przeciwieństwo typów ogólnych, w których w czasie wykonywania nie ma informacji o typie. W razie potrzeby kompilator wstawia instrukcje checkcast.

Ta różnica w pisaniu dla rodzajów ogólnych i tablic powoduje, że często nie nadaje się do mieszania tablic i typów ogólnych.

37

Za rozsądną realizacji Java:

Każdy obiekt ma nagłówek zawierający, między innymi, wskaźnik do typu wykonawczego (np Double lub String, ale może to nie być CharSequence lub AbstractList).Zakładając, że kompilator środowiska wykonawczego (zazwyczaj HotSpot w przypadku firmy Sun) nie może statycznie określić typu, pewne sprawdzenie musi zostać wykonane przez wygenerowany kod maszynowy.

Najpierw należy odczytać wskaźnik do typu środowiska wykonawczego. Jest to konieczne do wywołania metody wirtualnej w podobnej sytuacji.

Do rzutowania na typ klasy, wiadomo dokładnie, ile jest klas nadrzędnych, dopóki nie trafisz na java.lang.Object, więc typ można odczytać ze stałym przesunięciem względem wskaźnika typu (w rzeczywistości pierwszych ośmiu w HotSpot). Znowu jest to analogiczne do czytania wskaźnika metody wirtualnej metody.

Następnie odczytana wartość wymaga jedynie porównania z oczekiwanym statycznym typem rzutowania. W zależności od architektury zestawu instrukcji, inna instrukcja będzie musiała rozgałęzić (lub uszkodzić) niepoprawną gałąź. ISA, takie jak 32-bitowe ARM, mają instrukcje warunkowe i mogą mieć możliwość przejścia smutnej ścieżki przez szczęśliwą ścieżkę.

Interfejsy są trudniejsze ze względu na wielokrotne dziedziczenie interfejsu. Zwykle ostatnie dwa rzutowania na interfejsy są buforowane w typie środowiska wykonawczego. W bardzo wczesnych dniach (ponad dekadę temu) interfejsy były nieco powolne, ale to już nie ma znaczenia.

Mam nadzieję, że widać, że tego typu rzeczy są w dużej mierze nieistotne dla wydajności. Twój kod źródłowy jest ważniejszy. Pod względem wydajności największym hitem w twoim scenariuszu może być brak pamięci podręcznej z gonienia wskaźników obiektu w każdym miejscu (informacje o typie będą oczywiście powszechne).

+1

ciekawe - to znaczy, że dla klas nieinterfejsowych jeśli piszę podklasę Superclass sc = (Superclass); że kompilator (jit czyli: czas ładowania) będzie "statycznie" wstawiał odsunięcie od obiektu w każdej klasie superklasy i podklasie w swoich nagłówkach "klasy", a następnie poprzez proste dodawanie i porównywanie będzie w stanie rozwiązać problemy? - to fajnie i szybko :) Do interfejsów nie zakładałbym gorszego od małego hashtable czy btree? – peterk

+0

@peterk Do przesyłania między klasami nie zmienia się zarówno adres obiektu, jak i "vtbl" (tabela wskaźników metod, tabela hierarchii klas, pamięć podręczna interfejsu itp.). Tak więc rzut [klasa] sprawdza typ, a jeśli pasuje, nic innego nie musi się wydarzyć. –

1

Teoretycznie wprowadzono ogólne informacje. Jednak nowoczesne JVM są inteligentne. Każda implementacja jest inna, ale nie jest nierozsądne założenie, że może istnieć implementacja, która JIT zoptymalizowałaby kontrole odlewnicze, gdy mogłaby zagwarantować, że nigdy nie będzie konfliktu. Jeśli chodzi o konkretne JVM, nie mogę powiedzieć. Muszę przyznać, że chciałbym poznać specyfikę optymalizacji JIT samemu, ale są to dla inżynierów JVM martwić się.

Morał z tej historii polega na tym, aby najpierw napisać zrozumiały kod. Jeśli występują spowolnienia, profil i zidentyfikować problem. Szanse są dobre, że nie będzie to spowodowane rzutem. Nigdy nie rezygnuj z czystego, bezpiecznego kodu, próbując go zoptymalizować, ABY WIESZ, ŻE POTRZEBUJESZ.

Powiązane problemy