Question

Donc, je sais que try/catch ajoute une certaine surcharge et n'est donc pas un bon moyen de contrôler le flux du processus, mais d'où vient cette surcharge et quel est son impact réel ?

Était-ce utile?

La solution

Je ne suis pas un expert en implémentations de langage (alors prenez cela avec précaution), mais je pense que l'un des coûts les plus importants est de dérouler la pile et de la stocker pour la trace de la pile.Je soupçonne que cela se produit uniquement lorsque l'exception est levée (mais je ne sais pas), et si c'est le cas, cela représenterait un coût caché de taille décente à chaque fois qu'une exception est levée...donc ce n'est pas comme si vous sautiez simplement d'un endroit du 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 attendu dans le programme).

Autres conseils

Trois points à souligner ici :

  • Premièrement, il y a peu ou pas de pénalité de performance liée à la présence de blocs try-catch dans votre code.Cela ne devrait pas être pris en compte lorsque vous essayez d’éviter de les avoir dans votre candidature.L'impact sur les performances 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/à la réflexion se produisent afin de remplir les membres de la classe d'exception, tels que la trace de la pile. objet et les différents membres de type, etc.

  • Je crois que c'est l'une des raisons pour lesquelles le conseil général, si vous envisagez de rétablir l'exception, est simplement de throw; plutôt que de lancer à nouveau l'exception ou d'en construire une nouvelle, car dans ces cas, toutes les informations de la pile sont rassemblées alors que lors d'un simple lancement, elles sont toutes conservées.

Posez-vous des questions sur la surcharge liée à l'utilisation de try/catch/finally lorsque les exceptions ne sont pas levées, ou sur la surcharge liée à l'utilisation d'exceptions pour contrôler le flux de processus ?Cette dernière méthode 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 relèvent des domaines suivants :

  • Vous pouvez vous attendre à des échecs de cache supplémentaires en raison de l'exception levée accédant aux données résidentes qui ne se trouvent normalement pas dans le cache.
  • Vous pouvez vous attendre à des erreurs de page supplémentaires en raison de l'exception levée accédant au code non-résident et aux données qui ne figurent normalement pas dans l'ensemble de travail de votre application.

    • par exemple, le lancement de l'exception nécessitera que le CLR trouve l'emplacement des blocs final et catch en fonction de l'adresse IP actuelle et de l'adresse IP de retour de chaque trame jusqu'à ce que l'exception soit gérée ainsi que le bloc de filtre.
    • coût de construction supplémentaire et résolution de nom afin de créer les cadres à des fins de diagnostic, y compris la lecture des métadonnées, etc.
    • les deux éléments ci-dessus accèdent généralement au code et aux données « froids », des erreurs de page matérielles sont donc probables si vous avez une pression sur la mémoire :

      • le CLR essaie de placer le code et les données rarement utilisés loin des données fréquemment utilisées pour améliorer la localité, donc cela joue contre vous car vous forcez le froid à être chaud.
      • le coût des défauts de page, le cas échéant, éclipsera tout le reste.
  • Les situations de capture typiques sont souvent profondes, par conséquent les effets ci-dessus auraient tendance à être amplifiés (augmentant la probabilité de défauts de page).

Quant à l'impact réel du coût, il peut varier considérablement 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 à vos performances, vous rencontrez des problèmes en termes d'utilisation des exceptions au-delà de la simple performance.

D'après mon expérience, la plus grande surcharge consiste à lancer une exception et à 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 entraîné un ralentissement très visible lors de l'ouverture de nouveaux formulaires, etc.

Je l'ai donc refactorisé comme suit, qui - selon des mesures ultérieures rapides et sales - 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();
}

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.

Sans oublier que s'il s'agit d'une méthode fréquemment appelée, cela peut affecter le comportement global de l'application.
Par exemple, je considère l'utilisation de Int32.Parse comme une mauvaise pratique dans la plupart des cas car elle lève des exceptions pour quelque chose qui pourrait être facilement détecté 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 performances.
2) N'utilisez pas d'exceptions pour les erreurs exclues si vous pouvez l'éviter.

J'ai écrit un article à ce sujet il y a quelque temps parce qu'il y avait beaucoup de gens qui me 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 infime quantité de surcharge pour un bloc try/catch mais si petite qu'elle doit être ignorée.Cependant, si vous exécutez des blocs try/catch dans des boucles exécutées des millions de fois, vous souhaiterez peut-être envisager de déplacer le bloc en dehors de la boucle, si possible.

Le principal problème de performances avec les blocs try/catch est lorsque vous interceptez réellement une exception.Cela peut ajouter un retard notable à votre candidature.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 tout faire pour éviter qu’ils ne soient lancés.Vous ne devez pas les utiliser dans le cadre du flux attendu d’un programme qui fonctionne correctement.

J'ai fait un 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 prévu, mais gardez à l'esprit que ces résultats se trouvent sur une pile peu profonde.De plus, les premières exceptions sont très lentes.

Il est beaucoup plus facile d'écrire, de déboguer et de maintenir du 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 levées à un endroit et acceptées à un autre).Parce que c’est plus simple, le code sera en moyenne mieux écrit et moins buggé.

Pour moi, cette surcharge de programmeur 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 réelles.

Contrairement aux théories communément admises, try/catch peut avoir des implications significatives sur les performances, et cela dépend du fait qu'une exception soit levée ou non !

  1. Il désactive certaines optimisations automatiques (par conception), et dans certains cas injecte débogage code, comme on peut s'y attendre d'un aide au débogage.Il y aura toujours des gens qui ne seront pas d'accord avec moi sur ce point, mais le langage l'exige et le démontage le montre donc ces gens sont selon la définition du dictionnaire délirant.
  2. Cela peut avoir un impact négatif sur la maintenance. C'est en fait le problème le plus important ici, mais comme ma dernière réponse (qui portait presque entièrement sur lui) a été supprimée, je vais essayer de me concentrer sur le problème le moins important (la micro-optimisation) plutôt que sur le problème le plus important ( la macro-optimisation).

Le premier a été abordé dans quelques articles de blog par les MVP de Microsoft au fil des ans, et j'espère que vous pourrez les trouver facilement, mais StackOverflow s'en soucie. tellement à propos contenu je vais donc fournir des liens vers certains d'entre eux au fur et à mesure remplisseur preuve:

Il y a aussi cette réponse qui montre la différence entre le code démonté avec et sans utilisation try/catch.

Cela semble tellement évident qu'il y a est une surcharge qui est manifestement observable dans la génération de code, et cette surcharge semble même être reconnue par les personnes que Microsoft apprécie !Pourtant je le suis, répéter Internet...

Oui, il existe 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, il s'agit d'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 regrettable, car aucune économie de quelques nanosecondes ici et là de temps CPU n'est susceptible de compenser les nombreuses heures accumulées d'optimisation manuelle par les humains.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 et admettre qu'il est temps de simplement acheter un ordinateur plus rapide?

De toute évidence, nous devrions être optimiser nos priorités, pas seulement notre code !Dans ma dernière réponse, j'ai fait appel aux différences entre deux extraits de code.

En utilisant try/catch:

int x;
try {
    x = int.Parse("1234");
}
catch {
    return;
}
// some more code here...

N'utilise pas try/catch:

int x;
if (int.TryParse("1234", out x) == false) {
    return;
}
// some more code here

Considérez du point de vue d'un développeur de maintenance, ce qui est plus susceptible de vous faire perdre du temps, si ce n'est dans le profilage/l'optimisation (abordé ci-dessus), ce qui ne serait probablement même pas nécessaire sans le try/catch problème, puis en faisant défiler le code source...L’un d’eux contient quatre lignes supplémentaires de déchets passe-partout !

À mesure que de plus en plus de champs sont introduits dans une classe, tous ces déchets passe-partout 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 nous répéter ?Je suppose que nous pourrions cacher le try/catch derrière une abstraction faite maison, mais...alors autant éviter les exceptions (c.-à-d.utiliser Int.TryParse).

Ce n'est même pas un exemple complexe ;J'ai vu des tentatives pour instancier 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 autrement seraient automatiquement appliquées par le compilateur.Quelle meilleure façon de donner naissance à la théorie selon laquelle le compilateur est lent, par opposition à le compilateur fait exactement ce qu'on lui dit de faire?

En supposant qu'une exception soit levée par ledit constructeur et qu'un bug soit déclenché en conséquence, le mauvais développeur de maintenance doit alors le retrouver.Ce n'est peut-être pas une tâche si facile, car contrairement au code spaghetti du aller à cauchemar, try/catch peut causer des dégâts dans trois dimensions, car il pourrait remonter dans la pile non seulement dans d'autres parties de la même méthode, mais également dans d'autres classes et méthodes, qui seront toutes observées par le développeur de maintenance, à la dure!Pourtant on nous dit que "goto c'est dangereux", hein !

À la fin, je mentionne, try/catch a son avantage qui est, il est conçu pour désactiver les optimisations!C'est, si l'on veut, un 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 des optimisations qui pourraient autrement paralyser des algorithmes de transmission de messages sûrs et sensés pour les applications multithread, et pour détecter d'éventuelles conditions de concurrence ;) C'est à peu près le seul scénario auquel je puisse penser pour utiliser try/catch.Même cela a des alternatives.


À quoi servent les optimisations try, catch et finally désactiver?

ALIAS

Comment sont try, catch et finally utile comme aide au débogage ?

ce sont des barrières à l'écriture.Cela vient de la norme :

12.3.3.13 Instructions Try-Catch

Pour une déclaration 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 de essayer de bloquer 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 toute je) est identique à 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) v est définitivement affecté au point final de essayer de bloquer et chaque catch-block-i (pour chaque je de 1 à n).

Autrement dit, au début de chaque try déclaration:

  • toutes les affectations faites aux objets visibles avant d'entrer dans le try L'instruction doit être complète, ce qui nécessite un verrouillage de thread pour démarrer, ce qui la rend utile pour déboguer les conditions de concurrence !
  • le compilateur n'est pas autorisé à :
    • éliminer les affectations de variables inutilisées qui ont été définitivement affectées avant le try déclaration
    • réorganiser ou fusionner l'un d'entre eux affectations internes (c'est à dire.voir mon premier lien, si ce n'est pas déjà fait).
    • lever les affectations au-dessus de cette barrière, pour retarder l'affectation à une variable dont il sait qu'elle ne sera utilisée que plus tard (voire pas du tout) ou pour avancer de manière préventive les affectations ultérieures pour rendre d'autres optimisations possibles...

Une histoire similaire vaut pour chacun catch déclaration;supposez dans votre try instruction (ou un constructeur ou une fonction qu'elle invoque, etc.) que vous attribuez à cette variable autrement inutile (disons, garbage=42;), le compilateur ne peut pas éliminer cette instruction, même si elle n'a aucun rapport avec le comportement observable du programme.La mission doit avoir complété avant le catch le bloc est entré.

Pour ce que ça vaut, finally dit de la même manière dégradant histoire:

12.3.3.14 Instructions Try-finally

Pour un essayer déclaration stmt de la forme:

try try-block
finally finally-block

• L'état d'affectation défini de v au début de essayer de bloquer 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 enfin-bloquer 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 de essayer de bloquero v est définitivement affecté au point final de enfin-bloquerSi un transfert de flux de contrôle (tel qu'un aller à déclaration) est faite et commence dans essayer de bloquer, et se termine à l'extérieur de essayer de bloquer, alors v est également considéré comme définitivement affecté à ce transfert de flux de contrôle si v est définitivement affecté au point final de enfin-bloquer.(Ce n'est pas seulement si—si v est définitivement attribué pour une autre raison sur ce transfert de flux de contrôle, il est alors toujours considéré comme définitivement attribué.)

12.3.3.15 Instructions try-catch-finally

Analyse d'affectation définitive pour un essayer-attraper-enfin énoncé du formulaire :

try try-block
catch ( ... ) catch-block-1
... 
catch ( ... ) catch-block-n
finally finally-block

est fait comme si la déclaration était un essayer-enfin déclaration contenant un essayer-attraper déclaration:

try { 
    try   
    try-block
    catch ( ... ) catch-block-1
    ...   
    catch ( ... ) catch-block-n
} 
finally finally-block

J'aime beaucoup celui de Hafthor article de blog, et pour ajouter mon grain de sel à cette discussion, je voudrais dire qu'il a toujours été facile pour moi de faire en sorte que DATA LAYER ne lance qu'un seul type d'exception (DataAccessException).De cette façon, ma COUCHE BUSINESS sait à quelle exception s'attendre et l'attrape.Ensuite, en fonction d'autres règles commerciales (c'est-à-diresi mon objet métier participe au flux de travail, etc.), je peux lancer une nouvelle exception (BusinessObjectException) ou continuer sans relancer.

Je dirais de ne pas hésiter à utiliser try..catch chaque fois que cela est nécessaire et de l'utiliser à 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 aucune surcharge dans les cas courants, c'est-à-dire lorsqu'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 la bonne exception et cela se produira à chaque nouvelle exécution que vous effectuez.

Mais les exceptions sont des exceptions et ce coût est parfaitement acceptable.Si vous essayez de gérer les exceptions sans exceptions et d'utiliser des codes d'erreur de retour à la place, vous aurez probablement besoin d'une instruction if pour chaque sous-programme, ce qui 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 entrerez 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 coûts les plus importants 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 les espaces blancs insignifiants, on pourrait remarquer que ces deux morceaux de code équivalents ont presque exactement la même longueur en octets.Ce dernier contient 4 octets d'indentation en moins.Est-ce une mauvaise chose?

Pour ajouter l'insulte à l'injure, un étudiant décide de faire une boucle alors que l'entrée peut être analysée comme un int.La solution sans try/catch pourrait ressembler à ceci :

while (int.TryParse(...))
{
    ...
}

Mais à quoi cela ressemble-t-il lors de l'utilisation de 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 même pas la raison pour laquelle cela a échoué !Imaginez ce que ressent la personne effectuant le débogage, lorsque le code continue de s'exécuter malgré un défaut logique grave, plutôt que de s'arrêter avec une belle erreur d'exception évidente.Les blocs Try/Catch sont une validation/assainissement des données d'un homme paresseux.

L'un des coûts les plus mineurs 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 des optimisations qui pourraient autrement paralyser des algorithmes de transmission de messages sûrs et sensés pour les applications multithread, et pour détecter d'éventuelles conditions de concurrence ;) C'est à peu près le seul scénario auquel je puisse penser pour utiliser try/catch.Même cela a des alternatives.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top