Le casting Java introduit-il des frais généraux? Pourquoi?

105

Y a-t-il des frais généraux lorsque nous convertissons des objets d'un type à un autre? Ou le compilateur résout tout simplement et il n'y a aucun coût au moment de l'exécution?

S'agit-il de choses générales ou existe-t-il des cas différents?

Par exemple, supposons que nous ayons un tableau de Object [], où chaque élément peut avoir un type différent. Mais nous savons toujours avec certitude que, disons, l'élément 0 est un double, l'élément 1 est une chaîne. (Je sais que c'est une mauvaise conception, mais supposons simplement que je devais le faire.)

Les informations de type de Java sont-elles toujours conservées au moment de l'exécution? Ou tout est oublié après la compilation, et si nous faisons des éléments (Double) [0], nous suivrons simplement le pointeur et interpréterons ces 8 octets comme un double, quoi que ce soit?

Je ne sais pas très bien comment les types sont faits en Java. Si vous avez des recommandations sur des livres ou des articles, merci aussi.

Phil
la source
Les performances de l'instance et du casting sont assez bonnes. J'ai posté un timing en Java7 autour de différentes approches du problème ici: stackoverflow.com/questions/16320014/…
Wheezil
Cette autre question a de très bonnes réponses stackoverflow.com/questions/16741323/…
user454322

Réponses:

78

Il existe 2 types de casting:

Conversion implicite , lorsque vous transtypez d'un type vers un type plus large, ce qui se fait automatiquement et il n'y a pas de surcharge:

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

Casting explicite , lorsque vous passez d'un type plus large à un type plus étroit. Dans ce cas, vous devez explicitement utiliser la diffusion comme celle-ci:

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

Dans ce deuxième cas, il y a une surcharge au moment de l'exécution, car les deux types doivent être vérifiés et au cas où la conversion ne serait pas possible, JVM doit lever une ClassCastException.

Tiré de JavaWorld: le coût du casting

Le casting est utilisé pour convertir entre les types - entre les types de référence en particulier, pour le type d'opération de casting qui nous intéresse ici.

Les opérations de conversion ascendante (également appelées conversions élargies dans la spécification du langage Java) convertissent une référence de sous-classe en référence de classe ancêtre. Cette opération de conversion est normalement automatique, car elle est toujours sûre et peut être implémentée directement par le compilateur.

Les opérations de conversion descendante (également appelées conversions restrictives dans la spécification du langage Java) convertissent une référence de classe ancêtre en référence de sous-classe. Cette opération de conversion crée une surcharge d'exécution, car Java exige que la conversion soit vérifiée au moment de l'exécution pour s'assurer qu'elle est valide. Si l'objet référencé n'est pas une instance du type cible pour la conversion ou une sous-classe de ce type, la tentative de conversion n'est pas autorisée et doit lever une exception java.lang.ClassCastException.

Alex Ntousias
la source
102
Cet article de JavaWorld a plus de 10 ans, je prendrais donc toutes les déclarations qu'il fait sur les performances avec un très gros grain de votre meilleur sel.
skaffman
1
@skaffman, En fait, je prendrais toutes les déclarations qu'il fait (indépendamment de la performance ou non) avec un grain de sel.
Pacerier
sera-ce le même cas, si je n'attribue pas l'objet casté à la référence et que j'appelle simplement la méthode dessus? comme((String)o).someMethodOfCastedClass()
Parth Vishvajit
4
Maintenant, l'article a presque 20 ans. Et les réponses datent également de plusieurs années. Cette question nécessite une réponse moderne.
Raslanove
Et les types primitifs? Je veux dire, par exemple, la diffusion de int à short entraîne-t-elle une surcharge similaire?
luke1985
44

Pour une implémentation raisonnable de Java:

Chaque objet a un en-tête contenant, entre autres, un pointeur vers le type d'exécution (par exemple Doubleou String, mais il ne pourrait jamais être CharSequenceou AbstractList). En supposant que le compilateur d'exécution (généralement HotSpot dans le cas de Sun) ne peut pas déterminer le type de manière statique, une vérification doit être effectuée par le code machine généré.

Tout d'abord, ce pointeur vers le type d'exécution doit être lu. Ceci est de toute façon nécessaire pour appeler une méthode virtuelle dans une situation similaire.

Pour la conversion en un type de classe, on sait exactement combien de superclasses il y a jusqu'à ce que vous frappiez java.lang.Object, de sorte que le type peut être lu à un décalage constant par rapport au pointeur de type (en fait les huit premières dans HotSpot). Encore une fois, cela revient à lire un pointeur de méthode pour une méthode virtuelle.

Ensuite, la valeur de lecture a juste besoin d'une comparaison avec le type statique attendu de la distribution. En fonction de l'architecture du jeu d'instructions, une autre instruction devra brancher (ou faire une erreur) sur une branche incorrecte. Les ISA tels que ARM 32 bits ont une instruction conditionnelle et peuvent être capables de faire passer le triste chemin par le chemin heureux.

Les interfaces sont plus difficiles en raison de l'héritage multiple d'interface. Généralement, les deux derniers transtypages vers les interfaces sont mis en cache dans le type d'exécution. AU tout début (il y a plus de dix ans), les interfaces étaient un peu lentes, mais ce n'est plus pertinent.

J'espère que vous pouvez voir que ce genre de chose est largement sans rapport avec les performances. Votre code source est plus important. En termes de performances, le plus gros succès de votre scénario est susceptible d'être des échecs de cache dus à la poursuite des pointeurs d'objet partout (les informations de type seront bien sûr communes).

Tom Hawtin - Tacle
la source
1
intéressant - cela signifie donc que pour les classes non-interface, si j'écris Superclass sc = (Superclass) subclass; que le compilateur (jit ie: temps de chargement) mettra "statiquement" le décalage par rapport à Object dans chacune des Superclass et Subclass dans leurs en-têtes "Class" et ensuite via un simple add + compare pour pouvoir résoudre les choses? - c'est sympa et rapide :) Pour les interfaces, je suppose pas pire qu'une petite table de hachage ou btree?
peterk
@peterk Pour la conversion entre les classes, l'adresse de l'objet et le "vtbl" (table des pointeurs de méthode, plus table de la hiérarchie des classes, cache d'interface, etc.) ne changent pas. Donc, le cast [class] vérifie le type, et s'il correspond, rien d'autre ne doit se produire.
Tom Hawtin - tackline
8

Par exemple, supposons que nous ayons un tableau de Object [], où chaque élément peut avoir un type différent. Mais nous savons toujours avec certitude que, disons, l'élément 0 est un double, l'élément 1 est une chaîne. (Je sais que c'est une mauvaise conception, mais supposons simplement que je devais le faire.)

Le compilateur ne note pas les types des éléments individuels d'un tableau. Il vérifie simplement que le type de chaque expression d'élément est attribuable au type d'élément de tableau.

Les informations de type de Java sont-elles toujours conservées au moment de l'exécution? Ou tout est oublié après la compilation, et si nous faisons des éléments (Double) [0], nous suivrons simplement le pointeur et interpréterons ces 8 octets comme un double, quoi que ce soit?

Certaines informations sont conservées au moment de l'exécution, mais pas les types statiques des éléments individuels. Vous pouvez voir cela en regardant le format de fichier de classe.

Il est théoriquement possible que le compilateur JIT utilise une "analyse d'échappement" pour éliminer les vérifications de type inutiles dans certaines affectations. Cependant, faire cela dans la mesure que vous suggérez serait au-delà des limites d'une optimisation réaliste. Le gain de l'analyse des types d'éléments individuels serait trop faible.

De plus, les gens ne devraient pas écrire de code d'application comme ça de toute façon.

Stephen C
la source
1
Et les primitifs? (float) Math.toDegrees(theta)Y aura-t-il des frais généraux importants ici également?
SD
2
Il y a une surcharge pour certains moulages primitifs. Son importance dépend du contexte.
Stephen C
6

L'instruction de code d'octet pour effectuer la conversion au moment de l'exécution est appelée checkcast. Vous pouvez désassembler le code Java en utilisant javappour voir quelles instructions sont générées.

Pour les tableaux, Java conserve les informations de type au moment de l'exécution. La plupart du temps, le compilateur détecte les erreurs de type pour vous, mais il y a des cas où vous rencontrerez un ArrayStoreExceptionen essayant de stocker un objet dans un tableau, mais le type ne correspond pas (et le compilateur ne l'a pas détecté) . La spécification du langage Java donne l'exemple suivant:

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 = cpaest valide car ColoredPointest une sous-classe de Point, mais pa[0] = new Point()n'est pas valide.

Ceci est opposé aux types génériques, où aucune information de type n'est conservée lors de l'exécution. Le compilateur insère des checkcastinstructions si nécessaire.

Cette différence de typage pour les types génériques et les tableaux rend souvent inadapté le mélange de tableaux et de types génériques.

JesperE
la source
2

En théorie, des frais généraux sont introduits. Cependant, les JVM modernes sont intelligentes. Chaque implémentation est différente, mais il n'est pas déraisonnable de supposer qu'il pourrait exister une implémentation que JIT a optimisée en jetant les vérifications alors qu'elle pourrait garantir qu'il n'y aurait jamais de conflit. Quant aux JVM spécifiques qui proposent cela, je ne pourrais pas vous le dire. Je dois admettre que j'aimerais moi-même connaître les spécificités de l'optimisation JIT, mais ce sont les ingénieurs JVM qui doivent se soucier.

La morale de l'histoire est d'écrire d'abord un code compréhensible. Si vous rencontrez des ralentissements, profilez et identifiez votre problème. Il y a de bonnes chances que ce ne soit pas dû au casting. Ne sacrifiez jamais du code propre et sûr dans le but de l'optimiser JUSQU'À CE QUE VOUS SAVEZ QUE VOUS DEVEZ LE FAIRE.

HesNotTheStig
la source