Je n'arrête pas de voir des gens dire que les exceptions sont lentes, mais je ne vois aucune preuve. Donc, au lieu de demander si elles le sont, je vais demander comment fonctionnent les exceptions dans les coulisses, afin que je puisse décider quand les utiliser et si elles sont lentes.
D'après ce que je sais, les exceptions sont les mêmes que pour faire un retour plusieurs fois, sauf qu'il vérifie également après chaque retour s'il doit en faire un autre ou s'arrêter. Comment vérifie-t-il quand arrêter de revenir? Je suppose qu'il y a une deuxième pile qui contient le type de l'exception et un emplacement de pile, elle retourne ensuite jusqu'à ce qu'elle y arrive. Je suppose également que la seule fois que ce deuxième tapis est touché est sur un lancer et à chaque essai / prise. AFAICT implémenter un comportement similaire avec des codes de retour prendrait le même temps. Mais ce n'est qu'une supposition, alors je veux savoir ce qui se passe réellement.
Comment fonctionnent vraiment les exceptions?
Réponses:
Au lieu de deviner, j'ai décidé de regarder le code généré avec un petit morceau de code C ++ et une installation Linux quelque peu ancienne.
Je l'ai compilé avec
g++ -m32 -W -Wall -O3 -save-temps -c
et j'ai regardé le fichier d'assemblage généré._ZN11MyExceptionD1Ev
estMyException::~MyException()
, donc le compilateur a décidé qu'il avait besoin d'une copie non en ligne du destructeur.Surprise! Il n'y a aucune instruction supplémentaire sur le chemin de code normal. Le compilateur a plutôt généré des blocs de code de correction hors ligne supplémentaires, référencés via une table à la fin de la fonction (qui est en fait placée dans une section distincte de l'exécutable). Tout le travail est effectué en coulisses par la bibliothèque standard, basée sur ces tables (
_ZTI11MyException
istypeinfo for MyException
).OK, ce n'était pas vraiment une surprise pour moi, je savais déjà comment ce compilateur le faisait. Poursuivant la sortie de l'assemblage:
Ici, nous voyons le code pour lancer une exception. Bien qu'il n'y ait pas eu de frais généraux supplémentaires simplement parce qu'une exception pouvait être levée, il y a évidemment beaucoup de frais généraux à lancer et à attraper une exception. La majeure partie est cachée à l'intérieur
__cxa_throw
, ce qui doit:Comparez cela avec le coût du simple retour d'une valeur, et vous voyez pourquoi les exceptions ne devraient être utilisées que pour des retours exceptionnels.
Pour finir, le reste du fichier d'assemblage:
Les données typeinfo.
Encore plus de tableaux de gestion des exceptions et des informations supplémentaires assorties.
Donc, la conclusion, au moins pour GCC sous Linux: le coût est de l'espace supplémentaire (pour les gestionnaires et les tables), que des exceptions soient lancées ou non, plus le coût supplémentaire d'analyse des tables et d'exécution des gestionnaires lorsqu'une exception est levée. Si vous utilisez des exceptions au lieu de codes d'erreur et qu'une erreur est rare, cela peut être plus rapide , car vous n'avez plus la charge de tester les erreurs.
Si vous souhaitez plus d'informations, en particulier ce que font toutes les
__cxa_
fonctions, consultez la spécification d'origine dont elles proviennent:la source
La lenteur des exceptions était vraie autrefois.
Dans la plupart des compilateurs modernes, cela n'est plus vrai.
Remarque: ce n'est pas parce que nous avons des exceptions que nous n'utilisons pas non plus de codes d'erreur. Lorsque l'erreur peut être gérée localement, utilisez des codes d'erreur. Lorsque les erreurs nécessitent plus de contexte pour la correction, utilisez les exceptions: je l'ai écrit de manière beaucoup plus éloquente ici: Quels sont les principes qui guident votre politique de gestion des exceptions?
Le coût du code de gestion des exceptions lorsqu'aucune exception n'est utilisée est pratiquement nul.
Lorsqu'une exception est levée, du travail est effectué.
Mais vous devez comparer cela avec le coût de retour des codes d'erreur et de les vérifier jusqu'au point où l'erreur peut être traitée. Les deux prennent plus de temps à écrire et à maintenir.
Il y a aussi un piège pour les novices: bien
que les objets Exception soient censés être petits, certaines personnes y mettent beaucoup de choses. Ensuite, vous avez le coût de la copie de l'objet d'exception. La solution est double:
À mon avis, je parierais que le même code avec des exceptions est soit plus efficace, soit au moins aussi comparable que le code sans les exceptions (mais a tout le code supplémentaire pour vérifier les résultats des erreurs de fonction). N'oubliez pas que vous n'obtenez rien gratuitement, le compilateur génère le code que vous auriez dû écrire en premier lieu pour vérifier les codes d'erreur (et généralement le compilateur est beaucoup plus efficace qu'un humain).
la source
Vous pouvez implémenter des exceptions de différentes manières, mais elles reposent généralement sur une prise en charge sous-jacente du système d'exploitation. Sous Windows, il s'agit du mécanisme de gestion des exceptions structuré.
Il y a une discussion décente sur les détails sur Code Project: Comment un compilateur C ++ implémente la gestion des exceptions
La surcharge des exceptions se produit car le compilateur doit générer du code pour garder une trace des objets qui doivent être détruits dans chaque cadre de pile (ou plus précisément la portée) si une exception se propage hors de cette portée. Si une fonction n'a pas de variables locales sur la pile qui nécessitent l'appel de destructeurs, elle ne devrait pas avoir de pénalité de performances pour la gestion des exceptions.
L'utilisation d'un code de retour ne peut dérouler qu'un seul niveau de la pile à la fois, alors qu'un mécanisme de gestion des exceptions peut sauter beaucoup plus loin dans la pile en une seule opération s'il n'y a rien à faire dans les cadres intermédiaires de la pile.
la source
Matt Pietrek a écrit un excellent article sur la gestion structurée des exceptions Win32 . Bien que cet article ait été écrit à l'origine en 1997, il s'applique toujours aujourd'hui (mais ne s'applique bien sûr qu'à Windows).
la source
Cet article examine le problème et constate que dans la pratique, les exceptions coûtent au temps d'exécution, bien que le coût soit assez faible si l'exception n'est pas levée. Bon article, recommandé.
la source
Un de mes amis a écrit un peu comment Visual C ++ gère les exceptions il y a quelques années.
http://www.xyzw.de/c160.html
la source
Toutes les bonnes réponses.
Pensez également à combien il est plus facile de déboguer du code qui fait des «si vérifications» comme des portes en haut des méthodes au lieu de permettre au code de lever des exceptions.
Ma devise est qu'il est facile d'écrire du code qui fonctionne. Le plus important est d'écrire le code pour la prochaine personne qui le regarde. Dans certains cas, c'est vous dans 9 mois et vous ne voulez pas maudire votre nom!
la source