2016-03-27 12 views
7

Moje pytanie jest następujące:Czy wpisane tablice pomagają JIT w optymalizacji lepiej?

Jego zwykle dla kodu Java, że ​​zbiory ogólne realizowane jak:

public class GenericCollection<T> { 
    private Object[] data; 

    public GenericCollection() { 
     // Backing array is a plain object array. 
     this.data = new Object[10]; 
    } 

    @SuppressWarnings("unchecked") 
    public T get(int index) { 
     // And we just cast to appropriate type when needed. 
     return (T) this.data[index]; 
    } 
} 

i używane tak na przykład:

for (MyObject obj : genericCollection) { 
    obj.myObjectMethod(); 
} 

Od rodzajowe typu genericCollection jest wymazywany, JVM wydaje się nie mieć sposobu na stwierdzenie, że naprawdę wewnątrz "danych" tablicy genericCollection są tylko instancje MyObject, ponieważ rzeczywisty typ tablicy to Object, może być w niej String, a wywołanie "myObjectMethod" spowodowałoby wyjątek.

Zakładam więc, że JVM musi wykonać pewne ćwiczenia sprawdzające gimnastykę, aby wiedzieć, co naprawdę jest wewnątrz instancji GenericCollection.

Spójrzmy teraz na tej realizacji:

public class GenericCollection<T> { 
    private T[] data; 

    @SuppressWarnings("unchecked") 
    public GenericCollection (Class<T> type) { 
     // Create a type specific array. 
     this.data = (T[]) Array.newInstance(type, 10); 
    } 

    public T get (int index) { 
     // No unsafe casts needed. 
     return this.data[index]; 
    } 
} 

W tym przypadku tworzymy specyficzny tablicę typu poprzez odbicie, więc JVM mogła wywnioskować nie może być być tylko obiekty T wewnątrz tej tablicy w danym kontekście, dzięki czemu niebezpieczne rzuty i możliwe drogie kontrole typu są zbędne.

Moje pytanie byłoby, biorąc pod uwagę rzeczy, które HotSpot może zrobić, czy pomogłoby to w jakikolwiek sposób, pod względem wydajności, zaimplementować ogólne kolekcje z "właściwą" specyficzną tablicą?

Na przykład, czy pomaga HotSpot w usuwaniu niepotrzebnych kontroli typów lub rzutów? Być może prawdopodobnie umożliwia to łatwiejsze metody inline, ponieważ wie, że tablica podkładu jest określonego typu?

+0

hotspot JIT opiera się głównie na rodzajach zaobserwowanych przez profilowanie, a nie na informacji typu Java poziomu. Zatem tworzenie tablic wielu różnych typów może rzeczywiście tworzyć kod polimorficzny w niesklasyfikowanych ścieżkach kodowych. – the8472

Odpowiedz

6

Nie w tym przypadku.

Ogólna tablica T[] jest kasowana do Object[] w bajtodzie. Moduł pobierający dla Object[] zawsze zwraca Object, więc nie trzeba sprawdzać rzeczywistego typu tablicy. W związku z tym nie ma żadnej korzyści z posiadania T[] zamiast Object[] dla operacji pobierania tablicy. W obu przypadkach jest instrukcja aaload, a następnie checkcast i działa ona w ten sam sposób.

W międzyczasie ustawianie macierzy będzie działało gorzej dla tablic z typami niż dla Object[], ponieważ aastore musi sprawdzić, czy wartość jest zgodna z rzeczywistym typem komponentu tablicy.

Oznacza to, że Twój Proponowana modyfikacja działa równie dla get, ale wykonuje gorzej dla set. Można to potwierdzić następującym testem porównawczym JMH.

package bench; 

import org.openjdk.jmh.annotations.*; 

import java.lang.reflect.Array; 

@State(Scope.Benchmark) 
public class Generics { 
    private ObjectArray<String> objectArray; 
    private GenericArray<String> genericArray; 
    private StringArray stringArray; 
    private int index; 

    @Param("100000") 
    private int length; 

    @Setup 
    public void setup() { 
     genericArray = new GenericArray<>(String.class, length); 
     objectArray = new ObjectArray<>(length); 
     stringArray = new StringArray(length); 

     for (int i = 0; i < length; i++) { 
      String s = Integer.toString(i); 
      objectArray.set(i, s); 
      genericArray.set(i, s); 
      stringArray.set(i, s); 
     } 
    } 

    @Benchmark 
    public String getGenericArray() { 
     return genericArray.get(nextIndex()); 
    } 

    @Benchmark 
    public String getObjectArray() { 
     return objectArray.get(nextIndex()); 
    } 

    @Benchmark 
    public String getStringArray() { 
     return stringArray.get(nextIndex()); 
    } 

    @Benchmark 
    public void setGenericArray() { 
     genericArray.set(nextIndex(), "value"); 
    } 

    @Benchmark 
    public void setObjectArray() { 
     objectArray.set(nextIndex(), "value"); 
    } 

    @Benchmark 
    public void setStringArray() { 
     stringArray.set(nextIndex(), "value"); 
    } 

    private int nextIndex() { 
     if (++index == length) index = 0; 
     return index; 
    } 

    static class GenericArray<T> { 
     private T[] data; 

     @SuppressWarnings("unchecked") 
     public GenericArray(Class<T> type, int length) { 
      this.data = (T[]) Array.newInstance(type, length); 
     } 

     public T get(int index) { 
      return data[index]; 
     } 

     public void set(int index, T value) { 
      data[index] = value; 
     } 
    } 

    static class ObjectArray<T> { 
     private Object[] data; 

     public ObjectArray(int length) { 
      this.data = new Object[length]; 
     } 

     @SuppressWarnings("unchecked") 
     public T get(int index) { 
      return (T) data[index]; 
     } 

     public void set(int index, T value) { 
      data[index] = value; 
     } 
    } 

    static class StringArray { 
     private String[] data; 

     public StringArray(int length) { 
      this.data = new String[length]; 
     } 

     public String get(int index) { 
      return data[index]; 
     } 

     public void set(int index, String value) { 
      data[index] = value; 
     } 
    } 
} 

A wyniki:

Benchmark     (length) Mode Cnt Score Error Units 
Generics.getGenericArray 100000 avgt 40 5,212 ± 0,038 ns/op <- equal 
Generics.getObjectArray  100000 avgt 40 5,224 ± 0,043 ns/op <- 
Generics.getStringArray  100000 avgt 40 4,557 ± 0,051 ns/op 
Generics.setGenericArray 100000 avgt 40 3,299 ± 0,032 ns/op <- worse 
Generics.setObjectArray  100000 avgt 40 2,456 ± 0,007 ns/op <- 
Generics.setStringArray  100000 avgt 40 2,138 ± 0,008 ns/op 
+0

Świetna analiza. Interesujące jest to, dlaczego 'get' jest zawsze wolniejsze niż' set', nawet jeśli nie jest wymagana żadna kratka. Blackburn-glitch? –

+1

@TagirValeev Dokładnie. Każdy 'get' w tym benchmarku jest domyślnie śledzony przez' Blackhole.consume' (który oczywiście nie jest darmowy), podczas gdy 'set' nie jest. – apangin

+0

Przepraszamy za spóźnioną odpowiedź. Świetna odpowiedź, chociaż nie odnosi się do jednego punktu mojego pytania. "set" jest wolniejszy, ponieważ sprawdzanie typu. Metody "get" są takie same. Chodzi o to, że z "normalnym" generycznym, czy nie zapłaciłbyś kosztem typu testu, gdy ** przy użyciu ** zwróconego przedmiotu z kolekcji? "get" sam nie wymaga kontroli typu, ponieważ obiekt nie jest używany, ale zgaduję, że w przypadku mojego przykładu (obj.myObjectMethod(); podczas iteracji), kontrola typu musiałaby zostać wykonana w "normalny" ogólny przypadek przy każdym dostępie, ale można go było uniknąć w przypadku tablicy maszynowej. – TheStack

2

nr type erasure Java Tutorial wyjaśnia

generyczne zostały wprowadzone do języka Java, aby zapewnić bardziej rygorystyczne kontrole typu w czasie kompilacji oraz wspieranie programowania generycznego. Aby zaimplementować generyczne, kompilator Java stosuje typ wymazania do:

  • Zastąp wszystkie typy typów w typach ogólnych ich obwiedniami lub Obiektem, jeśli parametry typu są nieograniczone. Powstały kod bajtowy zawiera zatem tylko zwykłe klasy, interfejsy i metody.
  • Wstawić odlewy w razie potrzeby, aby zachować bezpieczeństwo typu.
  • Generowanie metod mostkowania w celu zachowania polimorfizmu w rozszerzonych typach ogólnych.

Zatem po kompilacji rodzajowe typy są Object.

+0

Generics są mechanizmem bezpieczeństwa typu "kompilacja czasu"; więc tak, w czasie wykonywania jest traktowane jako 'Object []'. –

+0

Tak, ogólny typ obiektu. Ale jak to tam mówi, kompilator wstawia rzutki typu tam, gdzie jest to konieczne (np. Pobieranie obiektu z kolekcji). Teraz spytałem, co dzieje się w * środowisku uruchomieniowym *. Tablica zostanie utworzona za pomocą odpowiedniego typu poprzez Array.newInstance (możesz ją wypróbować), więc pytam, czy HotSpot może zrobić coś ekstra z tego rodzaju informacją o typie, jak na przykład usuwanie niepotrzebnych sprawdzeń odlewów/typów. Specyfikacja wspomina tylko o wymazaniu typu w czasie kompilacji, ale nie określa, jakie rzeczy Hotspot może zrobić z dodatkowymi informacjami o typie w czasie wykonywania. – TheStack

+0

Generics nie pomagają, chociaż pomocne może być użycie prymitywnej tablicy zamiast tablicy Object. –

Powiązane problemy