Depuis la version 7 de JDK, j'utilise avec plaisir la méthode qu'elle a introduite pour rejeter les valeurs null
qui sont transmises à une méthode qui ne peut pas les accepter:
private void someMethod(SomeType pointer, SomeType anotherPointer) {
Objects.requireNonNull(pointer, "pointer cannot be null!");
Objects.requireNonNull(anotherPointer, "anotherPointer cannot be null!");
// Rest of method
}
Je pense que cette méthode donne un code très ordonné, facile à lire, et j'essaie d'encourager les collègues à l'utiliser. Mais un collègue (particulièrement averti) est résistant et dit que l'ancienne méthode est plus efficace:
private void someMethod(SomeType pointer, SomeType anotherPointer) {
if (pointer == null) {
throw new NullPointerException("pointer cannot be null!");
}
if (anotherPointer == null) {
throw new NullPointerException("anotherPointer cannot be null!");
}
// Rest of method
}
Il dit qu'appeler requireNonNull
implique de placer une autre méthode sur la pile d'appels de la JVM et entraînerait une performance inférieure à celle d'un simple == null
vérifier.
Donc, ma question est la suivante: y at-il une preuve d’une pénalité de performance encourue en utilisant le Objects.requireNonNull
méthodes?
Regardons l'implémentation de requireNonNull
dans le JDK d'Oracle:
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
Donc, c'est très simple . La machine virtuelle Java (Oracle, en tout cas) inclut un compilateur juste-à-temps optimisant à deux étapes pour convertir le code-octet en code machine. Des méthodes triviales comme celle-ci seront intégrées si cela permet d'obtenir de meilleures performances.
Donc, non, pas susceptible d'être plus lent, pas de manière significative, pas n'importe où qui serait important.
Ma question est donc la suivante: existe-t-il des preuves d’une pénalité de performance encourue en utilisant le
Objects.requireNonNull
méthodes?
Les seules preuves qui importeraient seraient les mesures de performance de votre codebase, ou de code conçu pour être très représentatif de celle-ci. Vous pouvez testez cela avec n'importe quel outil de test de performance décent, mais à moins que votre collègue ne puisse citer un exemple concret de problème de performance de votre base de code lié à cette méthode (plutôt qu'un benchmark synthétique). , J'aurais tendance à vous assumer et il/elle a de plus gros poissons à faire frire.
En passant, j'ai remarqué que votre exemple de méthode est une méthode private
. Donc, seul le code écrit par votre équipe l’appelle directement. Dans ces situations, vous pouvez vérifier si vous avez un cas d'utilisation pour assertions plutôt que des vérifications à l'exécution. Les assertions ont l'avantage de ne pas s'exécuter du tout dans le code "publié", et donc d'être plus rapides que l'une ou l'autre alternative dans votre question. De toute évidence, il existe des endroits où vous avez besoin de contrôles à l'exécution, mais ceux-ci se situent généralement aux points de contrôle, aux méthodes publiques, etc. Juste FWIW.
Officiellement, votre collègue a raison:
Si someMethod()
ou la trace correspondante n'est pas assez chaude, le code d'octet est interprété et un cadre de pile supplémentaire est créé.
Si someMethod()
est appelé au 9ème niveau de profondeur depuis le point chaud, les appels requireNonNull()
ne doivent pas être en ligne à cause de MaxInlineLevel
Option JVM
Si la méthode n'est pas en ligne pour l'un des motifs ci-dessus, argument de T.J. Crowder entre en jeu si vous utilisez la concaténation pour générer un message d'erreur
Même si requireNonNull()
est en ligne, JVM perd du temps et de l'espace pour effectuer cette opération.
D'autre part, il existe l'option FreqInlineSize
de la machine virtuelle Java, qui interdit d'inclure des méthodes trop grandes (en octets). Les bytecodes de la méthode sont comptés par eux-mêmes, sans comptabiliser la taille des méthodes, appelée dans cette méthode. Ainsi, extraire des morceaux de code dans des méthodes indépendantes peut parfois être utile. Dans l'exemple avec requireNonNull()
, cette extraction est déjà faite pour vous.
Si vous voulez preuve ... alors le moyen de l'obtenir est d'écrire un micro-repère.
(Je recommande de regarder d'abord le projet Calliper! Ou JMH ... selon la recommandation de Boris. Dans les deux cas, n'essayez pas d'écrire un micro-repère à partir de rien. Il y a trop de façons de vous tromper.)
Cependant, vous pouvez dire à votre collègue deux choses:
Le compilateur JIT fait du bon travail en ajoutant des appels de méthode de petite taille, et il est probable que cela se produira dans ce cas.
Si l'appel n'était pas en ligne, il est probable que la différence de performance ne serait que de 3 à 5 instructions, et il est très peu probable que cela fasse une différence significative.
Oui, il existe des preuves que la différence entre null
check manuel et Objects.requireNonNull()
manuel est négligeable. OpenJDK commiter Aleksey Shipilev a créé code de benchmarking qui le prouve en corrigeant JDK-8073479 , voici ses chiffres de conclusion et de performance:
TL;DR: Fear not, my little friends, use Objects.requireNonNull.
Stop using these obfuscating Object.getClass() checks,
those rely on non-related intrinsic performance, potentially
not available everywhere.
Runs are done on i5-4210U, 1.7 GHz, Linux x86_64, JDK 8u40 EA.
The explanations are derived from studying the generated code
("-prof perfasm" is your friend here), the disassembly is skipped
for brevity.
Out of box, C2 compiled:
Benchmark Mode Cnt Score Error Units
NullChecks.branch avgt 25 0.588 ± 0.015 ns/op
NullChecks.objectGetClass avgt 25 0.594 ± 0.009 ns/op
NullChecks.objectsNonNull avgt 25 0.598 ± 0.014 ns/op
Object.getClass() is intrinsified.
Objects.requireNonNull is perfectly inlined.
où branch
, objectGetClass
et objectsNonNull
sont définis comme suit:
@Benchmark
public void objectGetClass() {
o.getClass();
}
@Benchmark
public void objectsNonNull() {
Objects.requireNonNull(o);
}
@Benchmark
public void branch() {
if (o == null) {
throw new NullPointerException();
}
}
Effective Java par Joshua Bloch
Item 67: Optimiser judicieusement
Il y a trois aphorismes concernant l'optimisation que tout le monde devrait connaître:
Plus de péchés informatiques sont commis au nom de l'efficacité (sans nécessairement l'atteindre) que pour toute autre raison, y compris la stupidité aveugle.
- William A. Wulf [Wulf72]Nous devrions oublier les petites efficacités, disons environ 97% du temps: l'optimisation prématurée est la racine de tout mal.
- Donald E. Knuth [Knuth74]Nous suivons deux règles en matière d'optimisation:
Règle 1. Ne le faites pas.
Règle 2 (pour les experts uniquement). Ne le faites pas encore - c’est-à-dire tant que vous n’avez pas une solution parfaitement claire et non optimisée.
- M. A. Jackson [Jackson75]
Votre collègue a très probablement tort.
JVM est très intelligent et impliquera probablement la méthode Objects.requireNonNull(...)
. La performance est discutable, mais il y aura certainement des optimisations beaucoup plus sérieuses que cela.
Vous devez utiliser la méthode utilitaire de JDK.
En règle générale, la lisibilité et la maintenabilité devraient l'emporter sur l'optimisation.
Cette règle protège contre l'optimisation spéculative de la part de ceux qui pensent savoir comment fonctionne un compilateur même s'ils n'ont jamais essayé d'écrire un compilateur et qu'ils n'y ont jamais jeté un œil.
Votre collègue a tort, à moins qu'ils ne prouvent que la perte de performances est perceptible et intenable pour les utilisateurs.