Réponses:
Je ne suis pas un expert des implémentations de langage (alors prenez cela avec un grain de sel), mais je pense que l'un des plus gros coûts est de dérouler la pile et de la stocker pour la trace de la pile. Je soupçonne que cela ne se produit que lorsque l'exception est levée (mais je ne sais pas), et si tel est le cas, ce serait un coût caché de taille décente chaque fois qu'une exception est lancée ... donc ce n'est pas comme si vous sautiez d'un seul endroit dans le code à un autre, il se passe beaucoup de choses.
Je ne pense pas que ce soit un problème tant que vous utilisez des exceptions pour un comportement EXCEPTIONNEL (donc pas votre chemin typique et attendu à travers le programme).
Trois points à souligner ici:
Premièrement, il y a peu ou pas de pénalité de performances à avoir des blocs try-catch dans votre code. Cela ne devrait pas être une considération lorsque vous essayez d'éviter de les avoir dans votre application. Le hit de performance n'entre en jeu que lorsqu'une exception est levée.
Lorsqu'une exception est levée en plus des opérations de déroulement de la pile, etc. qui ont lieu et que d'autres ont mentionnées, vous devez être conscient que tout un tas de choses liées à l'exécution / réflexion se produisent afin de peupler les membres de la classe d'exception tels que la trace de pile objet et les différents membres de type etc.
Je pense que c'est l'une des raisons pour lesquelles le conseil général, si vous voulez renvoyer l'exception, est de simplement throw;
plutôt que de la renvoyer ou d'en construire une nouvelle, car dans ces cas, toutes ces informations de pile sont rassemblées alors que dans le simple jeter tout est préservé.
throw new Exception("Wrapping layer’s error", ex);
Êtes-vous en train de poser des questions sur la surcharge de l'utilisation de try / catch / finally lorsque les exceptions ne sont pas levées, ou la surcharge de l'utilisation d'exceptions pour contrôler le flux de processus? Ce dernier s'apparente un peu à l'utilisation d'un bâton de dynamite pour allumer la bougie d'anniversaire d'un tout-petit, et les frais généraux associés se situent dans les domaines suivants:
Vous pouvez vous attendre à des erreurs de page supplémentaires en raison de l'exception levée qui accède au code non résident et aux données qui ne figurent normalement pas dans le jeu de travail de votre application.
les deux éléments ci-dessus accèdent généralement au code et aux données "froids", donc des erreurs de page matérielles sont probables si vous avez une pression de mémoire du tout:
Quant à l'impact réel du coût, cela peut varier beaucoup en fonction de ce qui se passe dans votre code à ce moment-là. Jon Skeet a un bon résumé ici , avec quelques liens utiles. J'ai tendance à être d'accord avec sa déclaration selon laquelle si vous arrivez au point où les exceptions nuisent considérablement à votre performance, vous rencontrez des problèmes en termes d'utilisation d'exceptions au-delà de la simple performance.
D'après mon expérience, la plus grosse surcharge est de lancer une exception et de la gérer. J'ai déjà travaillé sur un projet dans lequel un code similaire au suivant était utilisé pour vérifier si quelqu'un avait le droit de modifier un objet. Cette méthode HasRight () était utilisée partout dans la couche de présentation et était souvent appelée pour des centaines d'objets.
bool HasRight(string rightName, DomainObject obj) {
try {
CheckRight(rightName, obj);
return true;
}
catch (Exception ex) {
return false;
}
}
void CheckRight(string rightName, DomainObject obj) {
if (!_user.Rights.Contains(rightName))
throw new Exception();
}
Lorsque la base de données de test s'est remplie de données de test, cela a conduit à un ralentissement très visible lors de l'ouverture de nouveaux formulaires, etc.
Je l'ai donc refactoré comme suit, qui - selon des mesures rapides et sales ultérieures - est environ 2 ordres de grandeur plus rapide:
bool HasRight(string rightName, DomainObject obj) {
return _user.Rights.Contains(rightName);
}
void CheckRight(string rightName, DomainObject obj) {
if (!HasRight(rightName, obj))
throw new Exception();
}
Donc, en bref, l'utilisation d'exceptions dans un flux de processus normal est environ deux ordres de grandeur plus lente que l'utilisation d'un flux de processus similaire sans exceptions.
Contrairement aux théories communément acceptées, try
/ catch
peut avoir des implications significatives sur les performances, et c'est de savoir si une exception est levée ou non!
Le premier a été couvert dans quelques messages de blog par Microsoft MVP au fil des ans, et je vous faire confiance pouvez les trouver facilement encore StackOverflow prend soin tant au sujet du contenu , donc je vais fournir des liens vers certains d'entre eux comme filler preuve:
try
/ catch
/finally
( et la deuxième partie ), par Peter Ritchie explore les optimisations quitry
/catch
/finally
désactive (et j'irai plus loin avec des citations de la norme)Parse
vs. TryParse
vs.ConvertTo
par Ian Huff déclare de manière flagrante que "la gestion des exceptions est très lente" et démontre ce point en opposantInt.Parse
et lesInt.TryParse
uns contre les autres ... Pour quiconque insiste sur le fait queTryParse
utilisetry
/catch
dans les coulisses, cela devrait faire la lumière!Il y a aussi cette réponse qui montre la différence entre le code désassemblé avec et sans utiliser try
/ catch
.
Il semble donc évident qu'il y a une surcharge qui est manifestement observable dans la génération de code, et que les frais généraux semble même être reconnu par les gens qui apprécient Microsoft! Pourtant, je répète Internet ...
Oui, il y a des dizaines d'instructions MSIL supplémentaires pour une ligne de code triviale, et cela ne couvre même pas les optimisations désactivées, donc techniquement, c'est une micro-optimisation.
J'ai posté une réponse il y a des années qui a été supprimée car elle se concentrait sur la productivité des programmeurs (la macro-optimisation).
C'est malheureux car aucune économie de quelques nanosecondes ici et là du temps CPU ne peut compenser de nombreuses heures d'optimisation manuelle par l'homme. Pour quoi votre patron paie-t-il le plus: une heure de votre temps ou une heure avec l'ordinateur en marche? À quel moment devons-nous débrancher la fiche et admettre qu'il est temps d' acheter un ordinateur plus rapide ?
De toute évidence, nous devrions optimiser nos priorités , pas seulement notre code! Dans ma dernière réponse, j'ai évoqué les différences entre deux extraits de code.
Utilisation de try
/ catch
:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
Ne pas utiliser try
/ catch
:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Considérez-le du point de vue d'un développeur de maintenance, ce qui est plus susceptible de vous faire perdre votre temps, sinon dans le profilage / optimisation (couvert ci-dessus), ce qui ne serait probablement même pas nécessaire s'il n'y avait pas le problème try
/ catch
, puis en faisant défiler code source ... L'un de ceux-ci a quatre lignes supplémentaires d'ordures standard!
Au fur et à mesure que de plus en plus de champs sont introduits dans une classe, toutes ces ordures standard s'accumulent (à la fois dans le code source et dans le code désassemblé) bien au-delà des niveaux raisonnables. Quatre lignes supplémentaires par champ, et ce sont toujours les mêmes lignes ... Ne nous a-t-on pas appris à éviter de se répéter? Je suppose que nous pourrions cacher le try
/ catch
derrière une abstraction faite maison, mais ... alors nous pourrions tout aussi bien éviter les exceptions (c'est-à-dire l'utilisation Int.TryParse
).
Ce n'est même pas un exemple complexe; J'ai vu des tentatives d'instanciation de nouvelles classes dans try
/ catch
. Considérez que tout le code à l'intérieur du constructeur pourrait alors être disqualifié de certaines optimisations qui seraient autrement appliquées automatiquement par le compilateur. Quelle meilleure façon de donner naissance à la théorie selon laquelle le compilateur est lent , par opposition au compilateur fait exactement ce qu'on lui dit de faire ?
En supposant qu'une exception est levée par ledit constructeur et qu'un bogue est déclenché en conséquence, le développeur de maintenance médiocre doit alors le localiser. Ce n'est peut-être pas une tâche facile, car contrairement au code spaghetti du cauchemar goto , try
/ catch
peut causer des dégâts en trois dimensions , car il pourrait monter dans la pile non seulement dans d'autres parties de la même méthode, mais aussi dans d'autres classes et méthodes , qui seront tous observés par le développeur de maintenance, à la dure ! Pourtant on nous dit que "goto est dangereux", hein!
À la fin, je mentionne que try
/ catch
a son avantage, c'est-à- dire qu'il est conçu pour désactiver les optimisations ! C'est, si vous voulez, une aide au débogage ! C'est pour cela qu'il a été conçu et c'est pour cela qu'il devrait être utilisé ...
Je suppose que c'est aussi un point positif. Il peut être utilisé pour désactiver les optimisations qui pourraient autrement paralyser des algorithmes de transmission de messages sûrs et sains pour les applications multithread, et pour détecter d'éventuelles conditions de course;) C'est à peu près le seul scénario auquel je puisse penser pour utiliser try / catch. Même cela a des alternatives.
Que faire Optimisations try
, catch
et finally
désactiver?
ALIAS
Comment sont try
, catch
et finally
des outils utiles de débogage?
ce sont des barrières d'écriture. Cela vient de la norme:
12.3.3.13 Instructions try-catch
Pour une instruction stmt de la forme:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n
- L'état d'affectation défini de v au début du bloc try est le même que l'état d'affectation défini de v au début de stmt .
- L'état d'affectation défini de v au début de catch-block-i (pour tout i ) est le même que l'état d'affectation défini de v au début de stmt .
- L'état d'affectation défini de v à l'extrémité de stmt est définitivement attribué si (et seulement si) v est définitivement affecté à l'extrémité de try-block et de chaque catch-block-i (pour chaque i de 1 à n ).
En d'autres termes, au début de chaque try
déclaration:
try
instruction doivent être terminées, ce qui nécessite un verrou de thread pour un début, ce qui le rend utile pour le débogage des conditions de course!try
instructionUne histoire similaire est valable pour chaque catch
déclaration; supposons que dans votre try
instruction (ou dans un constructeur ou une fonction qu'il invoque, etc.) vous affectez à cette variable autrement inutile (disons, garbage=42;
), le compilateur ne peut pas éliminer cette instruction, peu importe à quel point elle n'est pas pertinente pour le comportement observable du programme . L'affectation doit être terminée avant l' catch
entrée du bloc.
Pour ce que ça vaut, finally
raconte une histoire tout aussi dégradante :
12.3.3.14 Instructions try-finally
Pour une instruction try stmt de la forme:
try try-block finally finally-block
• L'état d'affectation défini de v au début du bloc try est le même que l'état d'affectation défini de v au début de stmt .
• L'état d'affectation défini de v au début du bloc final est le même que l'état d'affectation défini de v au début de stmt .
• L'état d'affectation défini de v au point final de stmt est définitivement attribué si (et seulement si) soit: o v est définitivement affecté au point final du bloc try o vest définitivement assigné au point final de finally-block Si un transfert de flux de contrôle (comme une instruction goto ) est effectué qui commence dans try-block et se termine en dehors de try-block , alors v est également considéré comme définitivement affecté à ce transfert de flux de contrôle si v est définitivement attribué au point final du bloc final . (Ce n'est pas seulement si - si v est définitivement attribué pour une autre raison lors de ce transfert de flux de contrôle, il est toujours considéré comme définitivement attribué.)
12.3.3.15 Instructions try-catch-finally
Analyse d'affectation définie pour une instruction try - catch - finally du formulaire:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n finally finally-block
est fait comme si l'instruction était une instruction try - finally comprenant une instruction try - catch :
try { try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n } finally finally-block
Sans parler du fait que ce soit à l'intérieur d'une méthode fréquemment appelée, cela peut affecter le comportement général de l'application.
Par exemple, je considère l'utilisation d'Int32.Parse comme une mauvaise pratique dans la plupart des cas, car elle lève des exceptions pour quelque chose qui peut être facilement capturé autrement.
Donc, pour conclure tout ce qui est écrit ici:
1) Utilisez les blocs try..catch pour détecter les erreurs inattendues - presque aucune pénalité de performance.
2) N'utilisez pas d'exceptions pour les erreurs exceptées si vous pouvez les éviter.
J'ai écrit un article à ce sujet il y a quelque temps parce que beaucoup de gens posaient des questions à ce sujet à l'époque. Vous pouvez le trouver ainsi que le code de test sur http://www.blackwasp.co.uk/SpeedTestTryCatch.aspx .
Le résultat est qu'il y a une petite quantité de surcharge pour un bloc try / catch mais si petite qu'elle devrait être ignorée. Cependant, si vous exécutez des blocs try / catch dans des boucles exécutées des millions de fois, vous pouvez envisager de déplacer le bloc en dehors de la boucle si possible.
Le problème de performances clé avec les blocs try / catch est lorsque vous interceptez réellement une exception. Cela peut ajouter un retard notable à votre demande. Bien sûr, lorsque les choses tournent mal, la plupart des développeurs (et de nombreux utilisateurs) reconnaissent la pause comme une exception sur le point de se produire! La clé ici est de ne pas utiliser la gestion des exceptions pour les opérations normales. Comme leur nom l'indique, ils sont exceptionnels et vous devez faire tout votre possible pour éviter qu'ils ne soient jetés. Vous ne devez pas les utiliser dans le cadre du flux attendu d'un programme qui fonctionne correctement.
J'ai fait une entrée de blog sur ce sujet l'année dernière. Vérifiez-le. En fin de compte, il n'y a presque aucun coût pour un bloc d'essai si aucune exception ne se produit - et sur mon ordinateur portable, une exception était d'environ 36 μs. C'est peut-être moins que ce à quoi vous vous attendiez, mais gardez à l'esprit que ces résultats se situent sur une pile peu profonde. De plus, les premières exceptions sont vraiment lentes.
try
/ catch
trop? Heh heh), mais vous semblez vous disputer avec la spécification du langage et certains MVP MS qui ont également écrit des blogs sur le sujet, fournissant des mesures au contraire de votre avis ... Je suis ouvert à l'idée que la recherche que j'ai faite est erronée, mais je vais avoir besoin de lire votre article de blog pour voir ce qu'il dit.
try-catch
bloc par rapport aux tryparse()
méthodes, mais le concept est le même.
Il est beaucoup plus facile d'écrire, de déboguer et de maintenir un code exempt de messages d'erreur du compilateur, de messages d'avertissement d'analyse de code et d'exceptions de routine acceptées (en particulier les exceptions qui sont levées à un endroit et acceptées à un autre). Parce que c'est plus simple, le code sera en moyenne mieux écrit et moins bogué.
Pour moi, cette surcharge de programmation et de qualité est le principal argument contre l'utilisation de try-catch pour le flux de processus.
La surcharge informatique des exceptions est insignifiante en comparaison, et généralement minime en termes de capacité de l'application à répondre aux exigences de performances du monde réel.
J'aime beaucoup l' article de blog de Hafthor , et pour ajouter mes deux cents à cette discussion, j'aimerais dire que, il a toujours été facile pour moi d'avoir le DATA LAYER lancer un seul type d'exception (DataAccessException). De cette façon, mon BUSINESS LAYER sait à quelle exception s'attendre et l'attrape. Ensuite, en fonction des règles métier supplémentaires (c'est-à-dire si mon objet métier participe au workflow, etc.), je peux lancer une nouvelle exception (BusinessObjectException) ou continuer sans relancer / lancer.
Je dirais que n'hésitez pas à utiliser try..catch chaque fois que cela est nécessaire et utilisez-le à bon escient!
Par exemple, cette méthode participe à un workflow ...
Commentaires?
public bool DeleteGallery(int id)
{
try
{
using (var transaction = new DbTransactionManager())
{
try
{
transaction.BeginTransaction();
_galleryRepository.DeleteGallery(id, transaction);
_galleryRepository.DeletePictures(id, transaction);
FileManager.DeleteAll(id);
transaction.Commit();
}
catch (DataAccessException ex)
{
Logger.Log(ex);
transaction.Rollback();
throw new BusinessObjectException("Cannot delete gallery. Ensure business rules and try again.", ex);
}
}
}
catch (DbTransactionException ex)
{
Logger.Log(ex);
throw new BusinessObjectException("Cannot delete gallery.", ex);
}
return true;
}
On peut lire dans Programming Languages Pragmatics de Michael L. Scott que les compilateurs actuels n'ajoutent aucun surcoût dans le cas courant, c'est-à-dire quand aucune exception ne se produit. Ainsi, chaque travail est effectué au moment de la compilation. Mais lorsqu'une exception est levée au moment de l'exécution, le compilateur doit effectuer une recherche binaire pour trouver l'exception correcte et cela se produira pour chaque nouveau lancement que vous avez effectué.
Mais les exceptions sont des exceptions et ce coût est parfaitement acceptable. Si vous essayez de gérer les exceptions sans exceptions et utilisez plutôt des codes d'erreur de retour, vous aurez probablement besoin d'une instruction if pour chaque sous-programme et cela entraînera une surcharge en temps réel. Vous savez qu'une instruction if est convertie en quelques instructions d'assemblage, qui seront exécutées à chaque fois que vous entrez dans vos sous-routines.
Désolé pour mon anglais, j'espère que cela vous aidera. Ces informations sont basées sur le livre cité, pour plus d'informations, reportez-vous au chapitre 8.5 Gestion des exceptions.
Analysons l'un des plus gros coûts possibles d'un bloc try / catch lorsqu'il est utilisé là où il ne devrait pas être utilisé:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
Et voici celui sans try / catch:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Sans compter l'espace blanc insignifiant, on pourrait remarquer que ces deux morceaux de code équivalents ont presque exactement la même longueur en octets. Ce dernier contient 4 octets en moins d'indentation. Est-ce une mauvaise chose?
Pour ajouter l'insulte à la blessure, un élève décide de boucler tandis que l'entrée peut être analysée comme un int. La solution sans try / catch pourrait être quelque chose comme:
while (int.TryParse(...))
{
...
}
Mais à quoi cela ressemble-t-il lorsque vous utilisez try / catch?
try {
for (;;)
{
x = int.Parse(...);
...
}
}
catch
{
...
}
Les blocs Try / catch sont des moyens magiques de gaspiller l'indentation, et nous ne savons toujours pas pourquoi cela a échoué! Imaginez ce que ressent la personne qui effectue le débogage, lorsque le code continue de s'exécuter au-delà d'une faille logique sérieuse, plutôt que de s'arrêter avec une erreur d'exception évidente. Les blocs Try / catch sont la validation / assainissement des données d'un paresseux.
L'un des coûts les plus faibles est que les blocs try / catch désactivent effectivement certaines optimisations: http://msmvps.com/blogs/peterritchie/archive/2007/06/22/performance-implications-of-try-catch-finally.aspx . Je suppose que c'est aussi un point positif. Il peut être utilisé pour désactiver les optimisations qui pourraient autrement paralyser des algorithmes de transmission de messages sûrs et sains pour les applications multithread, et pour détecter d'éventuelles conditions de course;) C'est à peu près le seul scénario auquel je puisse penser pour utiliser try / catch. Même cela a des alternatives.
Int.Parse
en faveur de Int.TryParse
.