36 votes

Pourquoi est-hashCode plus lente qu'une méthode similaire?

Normalement, Java optimise le virtuel appels sur la base du nombre de mises en œuvre rencontrés sur un appel de côté. Ceci peut être facilement vu dans les résultats de mon test, quand on regarde myCode, ce qui est une méthode triviale retour d'un stockées int. Il y a un trivial

static abstract class Base {
    abstract int myCode();
}

avec un couple identique de mise en œuvre comme

static class A extends Base {
    @Override int myCode() {
        return n;
    }
    @Override public int hashCode() {
        return n;
    }
    private final int n = nextInt();
}

Avec l'augmentation du nombre de mises en œuvre, le calendrier de l'appel de méthode se développe à partir de 0,4 ns à travers 1.2 ns pour les deux implémentations à 11,6 ns, puis se développe lentement. Lorsque la JVM a vu plusieurs de la mise en œuvre, c'est à dire, avec preload=true les horaires diffèrent légèrement (à cause d'un instanceof test nécessaire).

Jusqu'à présent tout est clair, cependant, l' hashCode se comporte assez différemment. En particulier, c'est 8 à 10 fois plus lent que dans trois cas. Aucune idée pourquoi?

Mise à JOUR

J'étais curieux de savoir si les pauvres hashCode pourrait être aidé par l'envoi manuellement, et il peut beaucoup.

timing

Un couple de branches a fait le travail parfaitement:

if (o instanceof A) {
    result += ((A) o).hashCode();
} else if (o instanceof B) {
    result += ((B) o).hashCode();
} else if (o instanceof C) {
    result += ((C) o).hashCode();
} else if (o instanceof D) {
    result += ((D) o).hashCode();
} else { // Actually impossible, but let's play it safe.
    result += o.hashCode();
}

Notez que le compilateur permet d'éviter une telle optimisations pour plus de deux mise en œuvre, comme la plupart des appels de méthode sont beaucoup plus cher qu'un simple champ de charge et le gain serait minime par rapport au code de la météorisation.

L'original de la question "Pourquoi ne pas JIT optimiser l' hashCode comme les autres méthodes" reste et hashCode2 preuves qu'il pouvait, en fait.

Mise à JOUR 2

Il ressemble à bestsss est bon, au moins avec cette remarque

l'appel de hashCode() de la classe de l'extension de Base est la même que l'appel de l'Objet.hashCode() et c'est de cette façon qu'il compile en bytecode, si vous ajoutez un explicite hashCode dans la Base de limiter le potentiel d'attirer les cibles en invoquant de la Base.hashCode().

Je ne suis pas complètement sûr de ce qu'il se passe, mais en déclarant Base.hashCode() fait hashCode à nouveau compétitifs.

results2

Mise à JOUR 3

OK, en fournissant un concrètes de mise en œuvre de l' Base#hashCode aide, cependant, l'équipe doit savoir qu'il n'est jamais appelée, comme toutes les sous-classes définies de leur propre (sauf si une autre sous-classe est chargée, ce qui peut conduire à un deoptimization, mais ce n'est rien de nouveau pour le JIT).

De sorte qu'il ressemble à un manqué d'optimisation de chance #1.

Fournir un résumé de la mise en œuvre de l' Base#hashCode fonctionne de la même façon. Cela est logique, car il fournit assure que plus de recherche est nécessaire que chaque sous-classe doit fournir sa propre (on ne peut pas simplement hériter de leur grand-parent).

Toujours pour plus de deux implémentations, myCode est donc beaucoup plus rapide, que le compilateur doit faire quelque chose subobtimal. Peut-être raté une optimisation de la chance #2?

4voto

bestsss Points 6403

hashCode est définie en java.lang.Object, afin de définir dans votre propre classe ne fait pas bien à tous. (c'est toujours une méthode définie, mais il ne fait aucune différence)

JIT a plusieurs façons d'optimiser les sites d'appel (dans ce cas - hashCode()):

  • pas de remplace - appel statique (pas virtuel du tout) - meilleur scénario avec plein d'optimisations
  • 2 sites - ByteBuffer par exemple: type exact de vérifier et statique de l'expédition. Le type de vérification est très simple, mais en fonction de l'utilisation qu'il peut ou ne peut pas être prédite par le matériel.
  • inline caches - lorsque les différentes instances de classe ont été utilisés en l'appelant corps, il est possible de les garder inline trop - que c'certaines méthodes pourraient être incorporé, certains peuvent être appelés via des tables virtuelles. Inline budget n'est pas très élevé. C'est exactement le cas dans la question - une autre méthode qui n'est pas nommé hashCode() serait fonction de la ligne caches, car il y a seulement quatre implémentations, au lieu de la v-table
  • L'ajout de plusieurs classes, en passant par l'appelant corps résultats réels d'appels virtuel, car le compilateur donne.

Le virtuel appels ne sont pas inline et nécessitent une indirection à travers la table des méthodes virtuelles et pratiquement assuré de cache miss. L'absence de l'in-lining fait le plein des stubs de fonction avec des paramètres passés par la pile. En général lorsque la performance réelle tueur est l'incapacité à en ligne et les appliquer à des optimisations.

Veuillez noter: l'appel de hashCode() de toute catégorie extension de la Base est la même que l'appel à la Object.hashCode() et c'est de cette façon qu'il compile en bytecode, si vous ajoutez un explicite hashCode dans la Base de limiter le potentiel d'attirer les cibles en invoquant Base.hashCode().

Beaucoup trop de classes (dans le JDK lui-même) ont hashCode() substituées dans les cas non inline table de hachage comme des structures de l'invocation est effectuée par l'intermédiaire vtable - c'est à dire de ralentir.

Comme bonus supplémentaire: lors du chargement de nouvelles classes de l'équipe a à deoptimize existant sites d'appel.


Je peut essayer de rechercher certaines sources, si quelqu'un est intéressé dans la poursuite de la lecture

3voto

apangin Points 4693

C'est un problème de performances: https://bugs.openjdk.java.net/browse/JDK-8014447
Il a été corrigé dans le JDK 8.

1voto

laune Points 8921

Je peux confirmer les résultats. Voir ces résultats (recompilations omis):

$ /extra/JDK8u5/jdk1.8.0_05/bin/java Main
overCode :    14.135000000s
hashCode :    14.097000000s

$ /extra/JDK7u21/jdk1.7.0_21/bin/java Main
overCode :    14.282000000s
hashCode :    54.210000000s

$ /extra/JDK6u23/jdk1.6.0_23/bin/java Main
overCode :    14.415000000s
hashCode :   104.746000000s

Les résultats sont obtenus en appelant les méthodes de la classe SubA extends Baseà plusieurs reprises. Méthode overCode() est identique à hashCode(), à la fois de qui il suffit de retourner un champ int.

Maintenant, la partie la plus intéressante: Si la méthode suivante est ajoutée à la classe de Base

@Override
public int hashCode(){
    return super.hashCode();
}

les temps d'exécution pour hashCode ne sont pas différents de ceux de l' overCode plus.

Base.java:

public class Base {
private int code;
public Base( int x ){
    code = x;
}
public int overCode(){
return code;
}
}

SubA.java:

public class SubA extends Base {
private int code;
public SubA( int x ){
super( 2*x );
    code = x;
}

@Override
public int overCode(){
return code;
}

@Override
public int hashCode(){
    return super.hashCode();
}
}

0voto

Dunes Points 6740

J'ai été à la recherche à votre invariants pour votre test. Il a scenario.vmSpec.options.hashCode set à 0. Selon ce diaporama (slide 37) que signifie Object.hashCode va utiliser un générateur de nombre aléatoire. Qui pourrait être pourquoi le compilateur JIT est de moins en moins intéressé par l'optimisation des appels d' hashCode car il estime qu'il est probable, il peut avoir recours à un coûteux appel de méthode, qui permettrait de compenser les gains de performance d'éviter une vtable de recherche.

Cela peut aussi être pourquoi paramètre Base à avoir son propre code de hachage méthode améliore les performances, car il empêche la possibilité de tomber à travers d' Object.hashCode.

http://www.slideshare.net/DmitriyDumanskiy/jvm-performance-options-how-it-works

-2voto

Eric Nicolas Points 343

La sémantique de hashCode() sont plus complexes que les méthodes ordinaires, de sorte que la JVM et le compilateur JIT doit faire plus de travail lorsque vous appelez hashCode() que lorsque vous appelez régulièrement une méthode virtuelle.

Une spécificité a un impact négatif sur les performances : l'appel de hashCode() sur un objet nul est valide et renvoie zéro. Cela nécessite une ramification que sur un appel normal qui, en soi, peut expliquer la différence de performance que vous avez constatés.

Remarque c'est vrai qu'il semble seulement à partir de Java 7 en raison de l'introduction de l'Objet.hashCode(cible) qui a cette sémantique. Il serait intéressant de savoir sur quelle version vous avez testé cette question, et si vous avez le même sur Java6 par exemple.

Une autre spécificité a un impact positif sur la performance : si vous ne fournissez pas votre propre hasCode() de la mise en œuvre, le compilateur JIT allez utiliser une ligne hashcode du code de calcul qui est plus rapide que régulier de l'Objet compilé.hashCode appel.

E.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X