Je regardais la gestion des erreurs systématiques en C ++ - Andrei Alexandrescu il prétend que les exceptions en C ++ sont très très lentes.
Est-ce toujours vrai pour C ++ 98?
Je regardais la gestion des erreurs systématiques en C ++ - Andrei Alexandrescu il prétend que les exceptions en C ++ sont très très lentes.
Est-ce toujours vrai pour C ++ 98?
Réponses:
Le modèle principal utilisé aujourd'hui pour les exceptions (Itanium ABI, VC ++ 64 bits) est le modèle d'exceptions à coût zéro.
L'idée est qu'au lieu de perdre du temps en mettant en place une garde et en vérifiant explicitement la présence d'exceptions partout, le compilateur génère une table d'appoint qui mappe tout point pouvant lever une exception (Program Counter) à une liste de gestionnaires. Lorsqu'une exception est levée, cette liste est consultée pour choisir le bon gestionnaire (le cas échéant) et la pile est déroulée.
Par rapport à la
if (error)
stratégie typique :if
lorsqu'une exception se produitLe coût, cependant, n'est pas anodin à mesurer:
dynamic_cast
test pour chaque gestionnaire)Donc, la plupart du temps, le cache manque, et donc pas trivial par rapport au code CPU pur.
Remarque: pour plus de détails, lisez le rapport TR18015, chapitre 5.4 Gestion des exceptions (pdf)
Donc, oui, les exceptions sont lentes sur le chemin exceptionnel , mais elles sont autrement plus rapides que les vérifications explicites (
if
stratégie) en général.Remarque: Andrei Alexandrescu semble remettre en question ce "plus rapide". J'ai personnellement vu les choses évoluer dans les deux sens, certains programmes étant plus rapides avec des exceptions et d'autres plus rapides avec des succursales, il semble donc effectivement y avoir une perte d'optimisabilité dans certaines conditions.
Est-ce que ça importe ?
Je dirais que non. Un programme doit être écrit avec la lisibilité à l'esprit, pas la performance (du moins, pas comme premier critère). Des exceptions doivent être utilisées lorsque l'on s'attend à ce que l'appelant ne puisse pas ou ne souhaite pas gérer l'échec sur place et le transmettre dans la pile. Bonus: en C ++ 11, les exceptions peuvent être rassemblées entre les threads en utilisant la bibliothèque standard.
C'est subtil cependant, je prétends qu'il
map::find
ne faut pas lancer mais je suis d'accord avec lemap::find
retour d'unchecked_ptr
qui jette si une tentative de déréférencement échoue parce que c'est nul: dans ce dernier cas, comme dans le cas de la classe qu'Alexandrescu a introduite, l'appelant choisit entre la vérification explicite et le recours à des exceptions. Donner du pouvoir à l'appelant sans lui donner plus de responsabilités est généralement un signe de bonne conception.la source
abort
vous permettra de mesurer l'empreinte de taille binaire et de vérifier que le load-time / i-cache se comporte de la même manière. Bien sûr, mieux vaut ne frapper aucun desabort
...Lorsque la question a été postée, j'étais en route pour le médecin, avec un taxi qui m'attendait, alors je n'ai eu le temps que pour un bref commentaire. Mais après avoir maintenant commenté et voté pour et contre, je ferais mieux d'ajouter ma propre réponse. Même si la réponse de Matthieu est déjà plutôt bonne.
Les exceptions sont-elles particulièrement lentes en C ++, par rapport aux autres langages?
Re la réclamation
Si c'est littéralement ce que prétend Andrei, alors pour une fois, il est très trompeur, sinon carrément faux. Pour une exception levée / levée est toujours lente par rapport aux autres opérations de base dans le langage, quel que soit le langage de programmation . Pas seulement en C ++ ou plus en C ++ que dans d'autres langages, comme l'indique la prétendue revendication.
En général, quelle que soit la langue, les deux fonctionnalités de base du langage qui sont des ordres de grandeur plus lentes que les autres, car elles se traduisent par des appels de routines qui gèrent des structures de données complexes, sont
lancement d'exceptions, et
allocation de mémoire dynamique.
Heureusement, en C ++, on peut souvent éviter les deux dans le code à temps critique.
Malheureusement, il n'y a rien de tel qu'un déjeuner gratuit , même si l'efficacité par défaut de C ++ est assez proche. :-) Pour l'efficacité gagnée en évitant le lancement d'exceptions et l'allocation de mémoire dynamique est généralement obtenue en codant à un niveau inférieur d'abstraction, en utilisant C ++ comme juste un «meilleur C». Et une abstraction inférieure signifie une plus grande «complexité».
Une plus grande complexité signifie plus de temps consacré à la maintenance et peu ou pas de bénéfice de la réutilisation du code, qui sont des coûts monétaires réels, même s'ils sont difficiles à estimer ou à mesurer. Par exemple, avec C ++, on peut, si on le souhaite, échanger une certaine efficacité du programmeur contre une efficacité d'exécution. Le fait de le faire est en grande partie une décision d'ingénierie et d'intuition, car en pratique, seul le gain, et non le coût, peut être facilement estimé et mesuré.
Existe-t-il des mesures objectives des performances de génération d'exceptions C ++?
Oui, le comité international de normalisation C ++ a publié un rapport technique sur les performances C ++, TR18015 .
Que signifie le fait que les exceptions sont «lentes»?
Cela signifie principalement qu'un
throw
peut prendre un temps très long ™ par rapport à par exemple uneint
affectation, en raison de la recherche de gestionnaire.Comme TR18015 l'explique dans sa section 5.4 «Exceptions», il existe deux stratégies principales de mise en œuvre de la gestion des exceptions,
l'approche où chaque
try
-bloc configure dynamiquement la capture d'exceptions, de sorte qu'une recherche dans la chaîne dynamique des gestionnaires soit effectuée lorsqu'une exception est levée, etl'approche où le compilateur génère des tables de recherche statiques qui sont utilisées pour déterminer le gestionnaire pour une exception levée.
La première approche très flexible et générale est presque forcée dans Windows 32 bits, tandis que dans le pays 64 bits et dans * nix-land, la deuxième approche beaucoup plus efficace est couramment utilisée.
Comme ce rapport l'indique également, pour chaque approche, il existe trois domaines principaux dans lesquels la gestion des exceptions a un impact sur l'efficacité:
try
-blocs,fonctions régulières (opportunités d'optimisation), et
throw
-expressions.Principalement, avec l'approche du gestionnaire dynamique (Windows 32 bits), la gestion des exceptions a un impact sur les
try
blocs, principalement quelle que soit la langue (car cela est forcé par le schéma de gestion des exceptions structuré de Windows ), tandis que l'approche de la table statique a un coût à peu près nul pourtry
- blocs. Discuter de cela prendrait beaucoup plus d'espace et de recherche que ce qui est pratique pour une réponse SO. Alors, consultez le rapport pour plus de détails.Malheureusement, le rapport, de 2006, est déjà un peu daté de la fin de 2012, et pour autant que je sache, il n'y a rien de comparable qui soit plus récent.
Une autre perspective importante est que l'impact de l' utilisation d'exceptions sur les performances est très différent de l'efficacité isolée des fonctionnalités de langage de prise en charge, car, comme le note le rapport,
Par exemple:
Coûts de maintenance dus aux différents styles de programmation (exactitude)
if
Vérification redondante des échecs du site d'appel par rapport à la centralisationtry
Problèmes de mise en cache (par exemple, un code plus court peut tenir dans le cache)
Le rapport a une liste différente d'aspects à prendre en compte, mais de toute façon, le seul moyen pratique d'obtenir des faits concrets sur l'efficacité de l'exécution est probablement d'implémenter le même programme en utilisant des exceptions et en n'utilisant pas d'exceptions, dans un délai déterminé sur le temps de développement, et avec les développeurs. familier avec chaque manière, puis MESURER .
Quel est un bon moyen d'éviter la surcharge des exceptions?
L'exactitude l'emporte presque toujours sur l'efficacité.
Sans exception, les événements suivants peuvent facilement se produire:
Un certain code P est destiné à obtenir une ressource ou à calculer des informations.
Le code appelant C aurait dû vérifier le succès / échec, mais ne le fait pas.
Une ressource inexistante ou des informations non valides sont utilisées dans le code suivant C, provoquant un chaos général.
Le principal problème est le point (2), où avec le schéma de code de retour habituel, le code appelant C n'est pas obligé de vérifier.
Il existe deux approches principales qui forcent une telle vérification:
Où P lève directement une exception en cas d'échec.
Où P renvoie un objet que C doit inspecter avant d'utiliser sa valeur principale (sinon une exception ou une terminaison).
La deuxième approche était, AFAIK, d'abord décrite par Barton et Nackman dans leur livre * Scientific and Engineering C ++: An Introduction with Advanced Techniques and Examples , où ils ont introduit une classe appelée
Fallow
pour un résultat de fonction «possible». Une classe similaire appeléeoptional
est maintenant offerte par la bibliothèque Boost. Et vous pouvez facilement implémenter uneOptional
classe vous-même, en utilisant unstd::vector
porteur de valeur pour le cas d'un résultat non POD.Avec la première approche, le code appelant C n'a d'autre choix que d'utiliser des techniques de gestion des exceptions. Avec la seconde approche, cependant, le code appelant C peut décider lui-même s'il doit effectuer
if
une vérification basée ou une gestion générale des exceptions. Ainsi, la deuxième approche permet de faire un compromis entre le programmeur et le temps d'exécution.Quel est l'impact des différents standards C ++ sur les performances des exceptions?
C ++ 98 a été le premier standard C ++. Pour les exceptions, il a introduit une hiérarchie standard des classes d'exceptions (malheureusement plutôt imparfaite). Le principal impact sur les performances était la possibilité de spécifications d'exception (supprimées dans C ++ 11), qui n'ont cependant jamais été entièrement implémentées par le compilateur Windows C ++ principal Visual C ++: Visual C ++ accepte la syntaxe de spécification d'exception C ++ 98, mais ignore simplement spécifications d'exception.
C ++ 03 n'était qu'un corrigendum technique de C ++ 98. La seule nouveauté en C ++ 03 était l' initialisation de la valeur . Ce qui n'a rien à voir avec les exceptions.
Avec le standard C ++ 11, les spécifications d'exception générale ont été supprimées et remplacées par
noexcept
mot clé.La norme C ++ 11 a également ajouté la prise en charge du stockage et de la relance des exceptions, ce qui est idéal pour propager les exceptions C ++ à travers les rappels en langage C. Cette prise en charge limite efficacement la manière dont l'exception actuelle peut être stockée. Cependant, pour autant que je sache, cela n'a pas d'impact sur les performances, sauf dans la mesure où dans le code plus récent, la gestion des exceptions peut plus facilement être utilisée des deux côtés d'un rappel en langage C.
la source
longjmp
le gestionnaire.try..finally
construction peut être implémentée sans déroulement de la pile. F #, C # et Java implémentent toustry..finally
sans utiliser le déroulement de la pile. Vous venezlongjmp
au gestionnaire (comme je l'ai déjà expliqué).Cela dépend du compilateur.
GCC, par exemple, était connu pour avoir de très mauvaises performances lors de la gestion des exceptions, mais cela s'est considérablement amélioré au cours des dernières années.
Mais notez que la gestion des exceptions devrait - comme son nom l'indique - être l'exception plutôt que la règle dans la conception de votre logiciel. Lorsque vous avez une application qui génère tellement d'exceptions par seconde que cela a un impact sur les performances et que cela est toujours considéré comme un fonctionnement normal, vous devriez plutôt penser à faire les choses différemment.
Les exceptions sont un excellent moyen de rendre le code plus lisible en éliminant tout ce code de gestion des erreurs maladroit, mais dès qu'elles font partie du flux normal du programme, elles deviennent vraiment difficiles à suivre. N'oubliez pas que a
throw
est à peu prèsgoto catch
déguisé.la source
throw new Exception
est un Java-ism. on ne devrait en règle générale jamais lancer de pointeurs.Vous ne pouvez jamais revendiquer les performances à moins de convertir le code en assembly ou de le comparer.
Voici ce que vous voyez: (quick-bench)
Le code d'erreur n'est pas sensible au pourcentage d'occurrence. Les exceptions ont un peu de surcharge tant qu'elles ne sont jamais lancées. Une fois que vous les jetez, la misère commence. Dans cet exemple, il est lancé pour 0%, 1%, 10%, 50% et 90% des cas. Lorsque les exceptions sont levées 90% du temps, le code est 8 fois plus lent que le cas où les exceptions sont levées 10% du temps. Comme vous le voyez, les exceptions sont vraiment lentes. Ne les utilisez pas s'ils sont lancés fréquemment. Si votre application n'a pas d'exigence en temps réel, n'hésitez pas à les lancer si elles se produisent très rarement.
Vous voyez de nombreuses opinions contradictoires à leur sujet. Mais finalement, les exceptions sont-elles lentes? Je ne juge pas. Regardez simplement la référence.
la source
Oui, mais cela n'a pas d'importance. Pourquoi?
Lis ça:
https://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx
Fondamentalement, cela dit que l'utilisation d'exceptions comme Alexandrescu décrites (ralentissement 50x parce qu'elles utilisent
catch
commeelse
) est tout simplement erronée. Cela étant dit pour les personnes qui aiment le faire comme ça, je souhaite que C ++ 22 :) ajoute quelque chose comme:(notez que ce devrait être un langage de base car il s'agit essentiellement d'un compilateur générant du code à partir d'un existant)
PS notez également que même si les exceptions sont aussi lentes ... ce n'est pas un problème si vous ne passez pas beaucoup de temps dans cette partie du code pendant l'exécution ... Par exemple si la division float est lente et que vous la faites 4x plus vite, peu importe si vous passez 0,3% de votre temps à faire la division FP ...
la source
Comme in silico a déclaré que son implémentation dépendait, mais en général, les exceptions sont considérées comme lentes pour toute implémentation et ne devraient pas être utilisées dans du code à haute performance.
EDIT: Je ne dis pas de ne pas les utiliser du tout, mais pour le code à haute performance, il est préférable de les éviter.
la source