Pourquoi les blocs d’essais sont-ils chers?

J’ai entendu le conseil que vous devriez éviter si possible, car ils sont chers.

Ma question concerne spécifiquement la plate-forme .NET: pourquoi les blocs d’essai sont-ils chers?

Résumé des réponses:

Il y a clairement deux camps sur cette question: ceux qui disent que les blocs d’essais sont chers, et ceux qui disent “peut-être un tout petit peu”.

Ceux qui disent que les blocs d’essai sont coûteux mentionnent généralement le “coût élevé” du déroulement de la stack d’appels. Personnellement, je ne suis pas convaincu par cet argument – spécialement après avoir lu comment les gestionnaires d’exceptions sont stockés ici .

Jon Skeet est membre du camp “peut-être un tout petit peu” et a écrit deux articles sur les exceptions et la performance que vous pouvez trouver ici .

Il y avait un article que j’ai trouvé extrêmement intéressant: il a parlé des “autres” implications de performance des blocs d’essais (pas nécessairement la consommation de mémoire ou de processeur). Peter Ritchie mentionne qu’il a trouvé que le code à l’intérieur des blocs d’essais n’est pas optimisé, car sinon le compilateur le ferait. Vous pouvez lire sur ses conclusions ici .

Enfin, il y a une entrée de blog sur le problème de l’homme qui a mis en place des exceptions dans le CLR. Allez voir l’article de Chris Brumme ici .

Ce n’est pas le bloc lui-même qui coûte cher, ni même une exception en soi, c’est cher, c’est le déroulement de la stack d’appels jusqu’à ce qu’il trouve un frame de stack capable de gérer l’exception. Lancer une exception est assez léger, mais si le runtime doit parcourir six frames de stack (c.-à-d. Six appels de méthode en profondeur) pour trouver un gestionnaire d’exceptions approprié, éventuellement en exécutant des blocs au fur et à mesure, .

Vous ne devriez pas éviter les blocs try / catch car cela signifie généralement que vous ne gérez pas correctement les exceptions qui pourraient survenir. La gestion structurée des exceptions (SEH) n’est coûteuse que lorsqu’une exception se produit, car le moteur d’exécution doit parcourir la stack d’appels à la recherche d’un gestionnaire catch, exécuter ce dernier (et il peut y en avoir plusieurs), puis exécuter les blocs retour au code au bon endroit.

Les exceptions ne sont pas destinées à contrôler la logique du programme, mais plutôt à indiquer des conditions d’erreur.

L’une des idées fausses les plus répandues sur les exceptions est qu’elles concernent des «conditions exceptionnelles». En réalité, elles concernent la communication de conditions d’erreur. Du sharepoint vue de la conception du cadre, une «condition exceptionnelle» n’existe pas. Qu’une condition soit exceptionnelle ou non dépend du contexte d’utilisation, mais les bibliothèques réutilisables savent rarement comment elles seront utilisées. Par exemple, OutOfMemoryException peut être exceptionnel pour une application de saisie de données simple; ce n’est pas si exceptionnel pour les applications qui font leur propre gestion de la mémoire (par exemple, un serveur SQL). En d’autres termes, la condition exceptionnelle d’un homme est la maladie chronique d’un autre homme. [http://blogs.msdn.com/kcwalina/archive/2008/07/17/ExceptionalError.aspx]

Un bloc d’essai n’est pas cher du tout. Peu ou pas de coûts sont encourus à moins qu’une exception soit levée. Et si une exception a été levée, c’est une circonstance exceptionnelle et vous ne vous souciez plus de la performance. Est-il important que votre programme prenne 0,001 seconde ou 1,0 seconde pour tomber? Non. Ce qui compte, c’est la qualité des informations qui vous sont rapscopes afin que vous puissiez y remédier et que cela ne se reproduise plus.

Je pense que les gens surestiment vraiment le coût de la performance en jetant des exceptions. Oui, il y a un succès, mais c’est relativement minime.

J’ai effectué le test suivant en lançant et en rattrapant un million d’exceptions. Il a fallu environ 20 secondes sur mon Intel Core 2 Duo , 2,8 GHz. C’est environ 50 000 exceptions par seconde. Si vous lancez même une petite fraction de cela, vous avez des problèmes d’architecture.

Voici mon code:

using System; using System.Diagnostics; namespace Test { class Program { static void Main(ssortingng[] args) { Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < 1000000; i++) { try { throw new Exception(); } catch {} } Console.WriteLine(sw.ElapsedMilliseconds); Console.Read(); } } } 

Le compilateur émet plus d’IL lorsque vous encapsulez du code dans un bloc try / catch; Regardez, pour le programme suivant:

 using System; public class Program { static void Main(ssortingng[] args) { Console.WriteLine("abc"); } } 

Le compilateur émettra cette IL:

 .method private hidebysig static void Main(ssortingng[] args) cil managed { .entrypoint // Code size 13 (0xd) .maxstack 8 IL_0000: nop IL_0001: ldstr "abc" IL_0006: call void [mscorlib]System.Console::WriteLine(ssortingng) IL_000b: nop IL_000c: ret } // end of method Program::Main 

Alors que pour la version légèrement modifiée:

 using System; public class Program { static void Main(ssortingng[] args) { try { Console.WriteLine("abc"); } catch { } } } 

émet plus:

 .method private hidebysig static void Main(ssortingng[] args) cil managed { .entrypoint // Code size 23 (0x17) .maxstack 1 IL_0000: nop .try { IL_0001: nop IL_0002: ldstr "abc" IL_0007: call void [mscorlib]System.Console::WriteLine(ssortingng) IL_000c: nop IL_000d: nop IL_000e: leave.s IL_0015 } // end .try catch [mscorlib]System.Object { IL_0010: pop IL_0011: nop IL_0012: nop IL_0013: leave.s IL_0015 } // end handler IL_0015: nop IL_0016: ret } // end of method Program::Main 

Tous ces NOP et autres coûtent.

IMO toute cette discussion est comme dire “les hauts sont chers parce que j’ai besoin d’incrémenter un compteur … je ne vais plus les utiliser”, ou “wow créer un object prend du temps, je ne vais pas créer une tonne d’objects plus. ”

En bout de ligne, vous ajoutez votre code, probablement pour une raison. Si les lignes de code n’entraînaient pas de surcharge, même si le cycle du processeur était le même, alors pourquoi existait-il? Rien n’est gratuit.

La meilleure chose à faire, comme pour toute ligne de code que vous ajoutez à votre application, est de ne la placer que si vous en avez besoin pour faire quelque chose. Si vous devez faire une capture, faites-le … comme si vous aviez besoin d’une chaîne pour stocker quelque chose, créez une nouvelle chaîne. De la même manière, si vous déclarez une variable qui n’a jamais été utilisée, vous perdez de la mémoire et des cycles de processeur pour la créer et vous devez la supprimer. même avec un try / catch.

En d’autres termes, s’il y a du code pour faire quelque chose, alors supposons que faire quelque chose va consumr du CPU et / ou de la mémoire.

Ce ne sont pas les blocs d’essais que vous devez vous préoccuper autant que les blocs de capture . Et puis, ce n’est pas que vous ne vouliez pas éviter d’écrire les blocs: c’est que vous voulez autant que possible écrire du code qui ne les utilisera jamais réellement.

Je doute qu’ils soient particulièrement chers. Souvent, ils sont nécessaires / requirejs.

Bien que je recommande fortement de les utiliser uniquement lorsque cela est nécessaire et au bon endroit / niveau d’imbrication au lieu de renvoyer l’exception à chaque retour d’appel.

J’imagine que la principale raison de cet avis était de dire que vous ne devriez pas utiliser les «try-catchs» où «sinon» serait une meilleure approche.

Ce n’est pas quelque chose dont je me soucierais jamais. Je préfère me soucier de la clarté et de la sécurité de l’essai… Enfin, je me bloque sur le fait que c’est «cher».

Personnellement, je n’utilise pas de 286, ni n’importe qui utilisant .NET ou Java non plus. Passez. Vous vous inquiétez de l’écriture d’un bon code qui affectera vos utilisateurs et autres développeurs au lieu de la structure sous-jacente qui fonctionne correctement pour 99,999999% des utilisateurs.

Ce n’est probablement pas très utile, et je ne veux pas être cinglant, mais simplement souligner la perspective.

Légèrement O / T, mais …

Il y a un concept de conception assez bon qui dit que vous ne devriez jamais exiger la gestion des exceptions. Cela signifie simplement que vous devriez pouvoir interroger n’importe quel object pour toutes les conditions susceptibles de provoquer une exception avant que cette exception soit levée.

Comme être capable de dire “writable ()” avant “write ()”, des trucs comme ça.

C’est une idée décente, et si elle est utilisée, elle fait que les exceptions vérifiées en Java semblent stupides – je veux dire, vérifier une condition et juste après, être obligé d’écrire encore un try / catch pour la même condition?

C’est un très bon modèle, mais les exceptions vérifiées peuvent être appliquées par le compilateur, ces vérifications ne le peuvent pas. En outre, toutes les bibliothèques ne sont pas créées à l’aide de ce modèle de conception – il s’agit simplement de quelque chose à garder à l’esprit lorsque vous envisagez des exceptions.

Chaque essai doit enregistrer beaucoup d’informations, par exemple des pointeurs de stack, des valeurs de registre CPU, etc. pour qu’il puisse dérouler la stack et ramener l’état qu’il avait lors du passage du bloc try au cas où une exception serait levée. Non seulement chaque essai doit enregistrer beaucoup d’informations, mais lorsqu’une exception est déclenchée, de nombreuses valeurs doivent être restaurées. Donc, essayer est très cher et un lancer / prise est très cher aussi.

Cela ne signifie pas que vous ne devriez pas utiliser les exceptions, cependant, en code de performance critique, vous ne devriez peut-être pas utiliser trop d’essais et ne pas lancer trop souvent des exceptions.