En Java, utiliser throw / catch comme une partie de la logique quand il n'y a pas réellement d'erreur est généralement une mauvaise idée (en partie) car lancer et intercepter une exception coûte cher, et le faire plusieurs fois dans une boucle est généralement beaucoup plus lent que les autres structures de contrôle qui n'impliquent pas de levée d'exceptions.
Ma question est, est-ce que le coût est encouru dans le lancer / intercepter lui-même, ou lors de la création de l'objet Exception (car il obtient beaucoup d'informations d'exécution, y compris la pile d'exécution)?
En d'autres termes, si je le fais
Exception e = new Exception();
mais ne le lancez pas, est-ce que c'est la majeure partie du coût du lancer, ou est-ce que le lancer + la prise en charge est ce qui coûte cher?
Je ne demande pas si mettre du code dans un bloc try / catch augmente le coût d'exécution de ce code, je demande si la capture de l'exception est la partie coûteuse, ou la création (appelant le constructeur pour) l'exception est la partie coûteuse .
Une autre façon de le demander est que si je crée une instance d'Exception et que je la lance et la rattrape encore et encore, est-ce que ce sera beaucoup plus rapide que de créer une nouvelle Exception à chaque fois que je lance?
la source
Réponses:
La création d' un objet d'exception n'est pas plus coûteuse que la création d'autres objets normaux. Le coût principal est caché dans la
fillInStackTrace
méthode native qui parcourt la pile d'appels et recueille toutes les informations nécessaires pour construire une trace de pile: classes, noms de méthode, numéros de ligne, etc.Le mythe des coûts d'exception élevés vient du fait que la plupart des
Throwable
constructeurs appellent implicitementfillInStackTrace
. Cependant, il existe un constructeur pour créer unThrowable
sans trace de pile. Il vous permet de créer des objets jetables très rapides à instancier. Une autre façon de créer des exceptions légères consiste à remplacerfillInStackTrace
.Qu'en est-il maintenant de lever une exception?
En fait, cela dépend de l'endroit où une exception levée est interceptée .
S'il est pris dans la même méthode (ou, plus précisément, dans le même contexte, car le contexte peut inclure plusieurs méthodes en raison de l'incrustation), il
throw
est aussi rapide et simple quegoto
(bien sûr, après la compilation JIT).Cependant, si un
catch
bloc se trouve quelque part plus profondément dans la pile, la JVM doit alors dérouler les trames de la pile, ce qui peut prendre beaucoup plus de temps. Cela prend encore plus de temps, s'il y a dessynchronized
blocs ou des méthodes impliqués, car le déroulement implique la libération des moniteurs appartenant aux trames de pile supprimées.Je pourrais confirmer les déclarations ci-dessus par des références appropriées, mais heureusement, je n'ai pas besoin de le faire, car tous les aspects sont déjà parfaitement couverts dans le poste de l'ingénieur de la performance de HotSpot Alexey Shipilev: The Exceptional Performance of Lil 'Exception .
la source
La première opération dans la plupart des
Throwable
constructeurs consiste à remplir la trace de la pile, où se trouve la majeure partie des dépenses.Il existe cependant un constructeur protégé avec un indicateur pour désactiver la trace de la pile. Ce constructeur est également accessible lors de l'extension
Exception
. Si vous créez un type d'exception personnalisé, vous pouvez éviter la création de trace de pile et obtenir de meilleures performances au détriment de moins d'informations.Si vous créez une seule exception de n'importe quel type par des moyens normaux, vous pouvez la renvoyer plusieurs fois sans avoir à surcharger la trace de la pile. Cependant, sa trace de pile reflétera où il a été construit, pas où il a été lancé dans un cas particulier.
Les versions actuelles de Java tentent d'optimiser la création de trace de pile. Le code natif est appelé pour remplir la trace de la pile, qui enregistre la trace dans une structure native plus légère. Java correspondants
StackTraceElement
objets sont paresseusement créés à partir de ce disque que lorsque lesgetStackTrace()
,printStackTrace()
ou d' autres méthodes qui nécessitent la trace sont appelés.Si vous éliminez la génération de trace de pile, l'autre coût principal consiste à dérouler la pile entre le lancer et le crochet. Moins il y aura de trames intermédiaires rencontrées avant que l'exception ne soit interceptée, plus ce sera rapide.
Concevez votre programme de sorte que les exceptions ne soient levées que dans des cas vraiment exceptionnels, et que les optimisations comme celles-ci soient difficiles à justifier.
la source
Theres un bon article sur les exceptions ici.
http://shipilev.net/blog/2014/exceptional-performance/
La conclusion étant que la construction de traces de pile et le déroulement de la pile sont les pièces coûteuses. Le code ci-dessous tire parti d'une fonctionnalité
1.7
permettant d'activer et de désactiver les traces de pile. Nous pouvons ensuite l'utiliser pour voir quel type de coûts différents scénarios ontLes temporisations suivantes concernent uniquement la création d'objets. J'ai ajouté
String
ici pour que vous puissiez voir que sans l'écriture de la pile, il n'y a presque aucune différence dans la création d'unJavaException
objet et d'unString
. Avec l'écriture de pile activée, la différence est dramatique, c'est-à-dire au moins un ordre de grandeur plus lent.Ce qui suit montre combien de temps il a fallu pour revenir d'un lancer à une profondeur particulière un million de fois.
Ce qui suit est presque certainement une simplification excessive ...
Si nous prenons une profondeur de 16 avec l'écriture de la pile, la création d'objet prend environ 40% du temps, la trace de la pile réelle en représente la grande majorité. ~ 93% de l'instanciation de l'objet JavaException est due à la trace de la pile prise. Cela signifie que le déroulement de la pile dans ce cas prend les 50% restants.
Lorsque nous désactivons la création d'objets de trace de pile, elle représente une fraction beaucoup plus petite, c'est-à-dire 20% et le déroulement de la pile représente désormais 80% du temps.
Dans les deux cas, le déroulement de la pile prend une grande partie du temps global.
Les cadres de pile dans cet exemple sont minuscules par rapport à ce que vous trouverez normalement.
Vous pouvez jeter un œil au bytecode en utilisant javap
c'est à dire pour la méthode 4 ...
la source
La création du
Exception
avec unenull
trace de pile prend autant de temps que lethrow
et letry-catch
bloc ensemble. Cependant, le remplissage de la trace de pile prend en moyenne 5 fois plus de temps .J'ai créé le benchmark suivant pour démontrer l'impact sur les performances. J'ai ajouté le
-Djava.compiler=NONE
à la configuration d'exécution pour désactiver l'optimisation du compilateur. Pour mesurer l'impact de la création de la trace de pile, j'ai étendu laException
classe pour tirer parti du constructeur sans pile:Le code de référence est le suivant:
Production:
Cela implique que la création d'un
NoStackException
est à peu près aussi coûteuse que son lancement répétéException
. Il montre également que la créationException
et le remplissage de sa trace de pile prennent environ 4 fois plus de temps.la source
Cette partie de la question ...
Semble demander si la création d'une exception et sa mise en cache quelque part améliorent les performances. Oui. C'est la même chose que de désactiver la pile en cours d'écriture lors de la création de l'objet car c'est déjà fait.
Voici les horaires que j'ai reçus, veuillez lire la mise en garde après cela ...
Bien sûr, le problème avec cela est que votre trace de pile indique maintenant où vous avez instancié l'objet et non d'où il a été jeté.
la source
En utilisant la réponse de @ AustinD comme point de départ, j'ai fait quelques ajustements. Code en bas.
En plus d'ajouter le cas où une instance d'exception est levée à plusieurs reprises, j'ai également désactivé l'optimisation du compilateur afin que nous puissions obtenir des résultats de performances précis. J'ai ajouté
-Djava.compiler=NONE
aux arguments VM, selon cette réponse . (Dans eclipse, modifiez la configuration d'exécution → arguments pour définir cet argument VM)Les resultats:
Ainsi, la création de l'exception coûte environ 5 fois plus que le lancer + l'attraper. En supposant que le compilateur n'optimise pas une grande partie du coût.
À titre de comparaison, voici le même test sans désactiver l'optimisation:
Code:
la source