2016-03-27 10 views
7

La mia domanda è la seguente:Gli array digitati aiutano il JIT a ottimizzare meglio?

la consueta per il codice Java per aver collezioni generiche implementato come:

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

e utilizzato in questo modo, per esempio:

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

Dal momento che il tipo generico di genericCollection viene cancellato, la JVM non sembra avere un modo per sapere che veramente all'interno dell'array 'data' di genericCollection ci sono solo istanze MyObject, dal momento che il tipo effettivo dell'array è Object, potrebbe esserci una String in esso, e chiamando "myObjectMethod" su di esso si alzava un'eccezione.

Quindi presumo che JVM debba eseguire alcuni esercizi di ginnastica di controllo runtime per sapere cosa c'è realmente all'interno dell'istanza di GenericCollection.

Ora controllare questa implementazione:

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

In questo caso è stata creata una matrice di tipo specifico attraverso la riflessione, in modo JVM potrebbe dedurre ci potrebbe essere solo essere oggetti T all'interno di tale matrice in un dato contesto, rendendo i cast pericolosi e le costose verifiche di tipo ridondanti.

La mia domanda sarebbe, date le cose che HotSpot può fare, sarebbe di aiuto in ogni modo, per quanto riguarda le prestazioni, implementare collezioni generiche con un array di backup specifico di tipo "corretto"?

Ad esempio, aiuta HotSpot a rimuovere controlli o cast di tipo non necessari? Forse lo si può abilitare a metodi inline più facilmente, dato che sa che l'array di supporto è di un tipo specifico?

+0

hotspot JIT si basa soprattutto sui tipi osservati da profilatura e non su informazioni di tipo a livello di java. Quindi la creazione di array di molti tipi diversi potrebbe effettivamente creare codice polimorfico in percorsi di codice non in linea – the8472

risposta

6

Non in questo caso particolare.

L'array generico T[] viene cancellato su Object[] nel bytecode. L'array getter per Object[] restituisce sempre Object, quindi non è necessario controllare il tipo effettivo di array. Quindi non vi è alcun vantaggio nell'avere T[] invece di Object[] per l'operazione di ottenimento dell'array. In entrambi i casi è presente l'istruzione aaload seguita da checkcast e funziona allo stesso modo.

Nel frattempo gamma setter si esibirà peggio per matrice tipizzata piuttosto che Object[], perché aastore deve verificare che il valore corrispondente al tipo di componente di matrice reale.

Cioè, la vostra proposta di modifica funziona altrettanto per get, ma esegue peggio per set. Questo può essere confermato dal seguente benchmark 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; 
     } 
    } 
} 

E i risultati:

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

Grande analisi. È interessante il motivo per cui get è sempre più lento di 'set' anche quando non è necessario alcun controllo. Problema tecnico relativo a Blackhole? –

+1

@TagirValeev Esattamente. Ogni "get" in questo benchmark è implicitamente seguito da "Blackhole.consume' (che non è gratuito, ovviamente), mentre" set "non lo è. – apangin

+0

Ci scusiamo per la risposta tardiva. Ottima risposta anche se non affronta un punto della mia domanda. "set" è più lento perché un controllo di tipo. i metodi "get" sono gli stessi. Il fatto è che, con un generico "normale", non pagheresti il ​​costo del controllo del tipo quando ** utilizzando ** l'oggetto restituito della raccolta? "get" da solo non incorre in un controllo di tipo perché l'oggetto non viene utilizzato, ma suppongo che nel caso del mio esempio (obj.myObjectMethod(); durante l'iterazione), il controllo del tipo debba essere eseguito in il caso generico "normale" su ciascun accesso, ma sarebbe evitato sul caso dell'array tipizzato. – TheStack

2

No. Il type erasure Java tutorial spiega

Generics sono state introdotte per il linguaggio Java per fornire controlli di tipo più stretti in fase di compilazione e per supportare la programmazione generica. Per implementare i generici, il compilatore Java applica la cancellazione di tipo a:

  • Sostituisci tutti i parametri di tipo in tipi generici con i relativi limiti o Oggetto se i parametri di tipo sono illimitati. Il codice prodotto, quindi, contiene solo classi, interfacce e metodi ordinari.
  • Inserire tipi di cast, se necessario, per preservare la sicurezza del tipo.
  • Generare metodi bridge per preservare il polimorfismo in tipi generici estesi.

Così, dopo la compilazione, i tipi generici sono Object.

+0

I generici sono un meccanismo di sicurezza del tipo a tempo compilato; così corretto, in fase di esecuzione è trattato come un 'oggetto []' comunque. –

+0

Sì, generico digita il suo oggetto. Ma come dice lì, il compilatore inserisce i cast di tipo dove è necessario (cioè, recuperare un oggetto dalla collezione). Ora, quello che ho chiesto è cosa succede a * runtime *. L'array viene creato con il tipo corretto tramite Array.newInstance (puoi provarlo), quindi quello che sto chiedendo è se HotSpot può fare qualcosa in più con quel tipo di informazioni sul tipo, come cancellare cancelli/controlli di tipo non necessari. La specifica menziona solo la cancellazione di tipo in fase di compilazione, ma non specifica il tipo di cose che HotSpot può fare con informazioni di tipo extra in fase di esecuzione. – TheStack

+0

I generici non aiutano, sebbene l'uso di una matrice primitiva invece di una matrice di oggetti può aiutare. –

Problemi correlati