2016-03-27 2 views
7

Ma question est la suivante:Les tableaux typés aident-ils le JIT à optimiser son fonctionnement?

Son habitude pour le code Java pour avoir des collections génériques mis en œuvre comme:

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

Et utilisé comme celui-ci par exemple:

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

Comme le type générique de genericCollection est effacé, la JVM ne semble pas avoir un moyen de savoir que vraiment dans le tableau 'data' de genericCollection il n'y a que des instances MyObject, puisque le type réel du tableau est Object, il peut y avoir un String, et appeler 'myObjectMethod' dessus soulèverait une exception.

Donc, je suppose que la JVM doit faire de la gymnastique de vérification de l'exécution pour savoir ce qu'il y a vraiment dans cette instance de GenericCollection.

Regardez maintenant cette implémentation:

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

Dans ce cas, nous créons un type matrice spécifique par la réflexion, de sorte que la machine virtuelle Java pourrait en déduire qu'il pourrait y avoir être seulement des objets T se trouvant dans ce tableau dans un contexte donné, ce qui les moulages dangereux et les chèques de type coûteux éventuels redondants. Ma question serait, étant donné les possibilités offertes par HotSpot, de mettre en œuvre, de quelque façon que ce soit, des implémentations de génériques avec une matrice de sauvegarde spécifique de type «appropriée»?

Par exemple, aide-t-il HotSpot à supprimer les vérifications de type ou les conversions inutiles? Peut-être peut-être l'activer pour des méthodes en ligne plus facilement étant donné qu'il sait que le tableau de support est d'un type spécifique?

+0

JIT est principalement basée sur les types observés par profilage et non sur des informations de type de niveau java. Ainsi, la création de tableaux de nombreux types différents pourrait en fait créer du code polymorphe dans des chemins de code non-alignés. – the8472

Répondre

6

Pas dans ce cas particulier.

Le tableau générique T[] est effacé à Object[] dans le bytecode. Le getter de tableau pour Object[] renvoie toujours Object, il n'a donc pas besoin de vérifier le type de tableau réel. Par conséquent, il n'y a aucun avantage à avoir T[] au lieu de Object[] pour l'opération d'obtention de tableau. Dans les deux cas, il y a aaload instruction suivie de checkcast, et cela fonctionne de la même manière.

Pendant ce temps setter tableau fonctionnera pire pour le tableau typé plutôt que Object[], car aastore doit vérifier que la valeur correspond au type de composant de tableau réel.

C'est, votre modification proposée fonctionne également pour get, mais effectue pire pour set. Cela peut être confirmé par le benchmark JMH suivant.

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

Et les résultats:

hotspot
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 analyse. Il est intéressant de savoir pourquoi 'get' est toujours plus lent que' set' même si aucune vérification n'est nécessaire. Pépin lié à Blackhole? –

+1

@TagirValeev Exactement. Chaque 'get' de ce benchmark est implicitement suivi de' Blackhole.consume' (ce qui n'est pas gratuit, bien sûr), alors que 'set' ne l'est pas. – apangin

+0

Désolé pour la réponse tardive. Bonne réponse même si elle ne répond pas à un point de ma question. "set" est plus lent car une vérification de type. Les méthodes "get" sont les mêmes. La chose est, avec un générique "normal", ne paieriez-vous pas le coût du contrôle de type lorsque ** en utilisant ** l'objet retourné de la collection? "get" seul n'entraînera pas de vérification de type parce que l'objet n'est pas utilisé, mais je devine que dans le cas de mon exemple (obj.myObjectMethod(), pendant l'itération), le contrôle de type devrait être fait dans le cas générique "normal" sur chaque accès, mais serait évité sur le cas du tableau typé. – TheStack

2

Non, le tutoriel Java type erasure explique

ont été introduites

Génériques au langage Java pour fournir des contrôles plus stricts de type à la compilation et pour soutenir la programmation générique. Pour implémenter les génériques, le compilateur Java applique l'effacement de type à:

  • Remplacer tous les paramètres de type dans les types génériques par leurs bornes ou Object si les paramètres de type sont non bornés. Le bytecode produit contient donc uniquement des classes, des interfaces et des méthodes ordinaires.
  • Insérer des moulages de type si nécessaire pour préserver la sécurité du type.
  • Génère des méthodes de pont pour préserver le polymorphisme dans les types génériques étendus.

Ainsi, après la compilation, les types génériques sont Object.

+0

Les génériques sont un mécanisme de sécurité de type temps de compilation; donc correct, au moment de l'exécution, il est traité comme un 'Object []' de toute façon. –

+0

Oui, générique tapez son objet. Mais comme il est dit ici, le compilateur insère le type de moulage là où c'est nécessaire (c'est-à-dire récupérer un objet de la collection). Maintenant, ce que j'ai demandé est ce qui se passe à * runtime *. Le tableau est créé avec le bon type via Array.newInstance (vous pouvez l'essayer), ce que je demande, c'est si HotSpot peut faire quelque chose de plus avec ce type d'information de type, comme l'effacement de moulages/contrôles de type inutiles. La spécification mentionne seulement l'effacement de type au moment de la compilation, mais elle ne spécifie pas le type de choses que HotSpot peut faire avec des informations de type supplémentaires à l'exécution. – TheStack

+0

Les génériques n'aident pas, mais l'utilisation d'un tableau primitif au lieu d'un tableau Object peut aider. –