Est-ce que les blocs try/catch nuisent à la performance quand les exceptions ne sont pas lancées?

lors d'une révision de code avec un employé de Microsoft, nous avons rencontré une grande section de code à l'intérieur d'un bloc try{} . Elle et un représentant de la TI ont laissé entendre que cela peut avoir des effets sur la performance du code. En fait, ils ont suggéré que la plus grande partie du code devrait être en dehors des blocs "essayer/attraper", et que seules les sections importantes devraient être vérifiées. L'employé de Microsoft a ajouté et a dit qu'un livre blanc à venir met en garde contre les blocs d'essai/attrape incorrects.

j'ai regardé autour et trouvé il peut affecter les optimisations , mais il semble s'appliquer seulement quand une variable est partagée entre les portées.

Je ne pose pas de questions sur la maintenabilité du code, ni même sur le traitement des bonnes exceptions (le code en question nécessite une refonte, sans doute). Je ne parle pas non plus de l'utilisation d'exceptions pour le contrôle du débit, ce qui est clairement faux dans la plupart des cas. Ce sont des questions importantes (certains sont plus importants), mais pas l'objectif ici.

comment les blocs try/catch affectent-ils la performance lorsque les exceptions sont et non lancées?

EDIT: j'ajoute une prime. Il y a des réponses intéressantes, mais j'aimerais obtenir d'autres commentaires.

215
demandé sur John Saunders 2009-08-20 23:50:23
la source

10 ответов

Vérifiez.

static public void Main(string[] args)
{
    Stopwatch w = new Stopwatch();
    double d = 0;

    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        try
        {
            d = Math.Sin(1);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }

    w.Stop();
    Console.WriteLine(w.Elapsed);
    w.Reset();
    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        d = Math.Sin(1);
    }

    w.Stop();
    Console.WriteLine(w.Elapsed);
}

sortie:

00:00:00.4269033  // with try/catch
00:00:00.4260383  // without.

en millisecondes:

449
416

nouveau code:

for (int j = 0; j < 10; j++)
{
    Stopwatch w = new Stopwatch();
    double d = 0;
    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        try
        {
            d = Math.Sin(d);
        }

        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }

        finally
        {
            d = Math.Sin(d);
        }
    }

    w.Stop();
    Console.Write("   try/catch/finally: ");
    Console.WriteLine(w.ElapsedMilliseconds);
    w.Reset();
    d = 0;
    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        d = Math.Sin(d);
        d = Math.Sin(d);
    }

    w.Stop();
    Console.Write("No try/catch/finally: ");
    Console.WriteLine(w.ElapsedMilliseconds);
    Console.WriteLine();
}

nouveaux résultats:

   try/catch/finally: 382
No try/catch/finally: 332

   try/catch/finally: 375
No try/catch/finally: 332

   try/catch/finally: 376
No try/catch/finally: 333

   try/catch/finally: 375
No try/catch/finally: 330

   try/catch/finally: 373
No try/catch/finally: 329

   try/catch/finally: 373
No try/catch/finally: 330

   try/catch/finally: 373
No try/catch/finally: 352

   try/catch/finally: 374
No try/catch/finally: 331

   try/catch/finally: 380
No try/catch/finally: 329

   try/catch/finally: 374
No try/catch/finally: 334
163
répondu Ben M 2014-04-02 15:44:28
la source

après avoir vu toutes les statistiques pour avec try/catch et sans try/catch, curiosity m'a forcé à regarder derrière pour voir ce qui est généré pour les deux cas. Voici le code:

C#:

private static void TestWithoutTryCatch(){
    Console.WriteLine("SIN(1) = {0} - No Try/Catch", Math.Sin(1)); 
}

MSIL:

.method private hidebysig static void  TestWithoutTryCatch() cil managed
{
  // Code size       32 (0x20)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "SIN(1) = {0} - No Try/Catch"
  IL_0006:  ldc.r8     1.
  IL_000f:  call       float64 [mscorlib]System.Math::Sin(float64)
  IL_0014:  box        [mscorlib]System.Double
  IL_0019:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)
  IL_001e:  nop
  IL_001f:  ret
} // end of method Program::TestWithoutTryCatch

C#:

private static void TestWithTryCatch(){
    try{
        Console.WriteLine("SIN(1) = {0}", Math.Sin(1)); 
    }
    catch (Exception ex){
        Console.WriteLine(ex);
    }
}

MSIL:

.method private hidebysig static void  TestWithTryCatch() cil managed
{
  // Code size       49 (0x31)
  .maxstack  2
  .locals init ([0] class [mscorlib]System.Exception ex)
  IL_0000:  nop
  .try
  {
    IL_0001:  nop
    IL_0002:  ldstr      "SIN(1) = {0}"
    IL_0007:  ldc.r8     1.
    IL_0010:  call       float64 [mscorlib]System.Math::Sin(float64)
    IL_0015:  box        [mscorlib]System.Double
    IL_001a:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                  object)
    IL_001f:  nop
    IL_0020:  nop
    IL_0021:  leave.s    IL_002f //JUMP IF NO EXCEPTION
  }  // end .try
  catch [mscorlib]System.Exception 
  {
    IL_0023:  stloc.0
    IL_0024:  nop
    IL_0025:  ldloc.0
    IL_0026:  call       void [mscorlib]System.Console::WriteLine(object)
    IL_002b:  nop
    IL_002c:  nop
    IL_002d:  leave.s    IL_002f
  }  // end handler
  IL_002f:  nop
  IL_0030:  ret
} // end of method Program::TestWithTryCatch

Je ne suis pas un expert en IL mais nous pouvons voir qu'un objet d'exception locale est créé sur la quatrième ligne .locals init ([0] class [mscorlib]System.Exception ex) après que les choses sont assez les mêmes que pour la méthode sans essayer/attraper jusqu'à la ligne dix-sept IL_0021: leave.s IL_002f . Si une exception se produit, le contrôle saute à la ligne IL_0025: ldloc.0 sinon nous sautons à l'étiquette IL_002d: leave.s IL_002f et la fonction retourne.

je peux en toute sécurité supposer que si aucune exception ne se produit, alors c'est au-dessus de la création de variables locales pour tenir les objets d'exception seulement et une instruction de saut.

80
répondu TheVillageIdiot 2009-09-01 14:30:11
la source

Pas de. Si les optimisations triviales qu'un bloc try/finally empêche ont réellement un impact mesurable sur votre programme, vous ne devriez probablement pas utiliser .NET en premier lieu.

51
répondu John Kugelman 2009-08-21 00:00:25
la source

explication très complète du modèle d'exception .NET.

Rico Mariani Performances de Tidbits: Exception des Coûts: Quand à jeter et quand ne pas le faire

le premier type de coût est la statique coût de la gestion des exceptions dans votre code. Exceptions gérées en fait faire comparativement bien ici, je veux dire le coût statique peut être beaucoup plus faible que dire en C++. Pourquoi cette? Eh bien, le coût statique est vraiment engagés dans deux types de lieux: Tout d'abord, le site de try/finally/attraper/lancer là où il y a code pour ces constructions. Deuxièmement, dans code non modifié, il y a le furtif coûts associés au suivi des tous les objets qui doivent être détruits dans le cas où un l'exception est lancée. Il y a un quantité considérable de logique de nettoyage qui doit être présent et le sournois une partie est que même le code qui ne m' lancer ou attraper ou autrement ont manifeste utilisation des exceptions encore supporte le fardeau de savoir comment nettoyer après lui-même.

Dmitriy Zaslavskiy:

selon la note de Chris Brumme: il y a aussi, un coût liées au fait que le certaines optimisations ne sont pas réalisé par JIT en présence de catch

30
répondu arul 2009-08-21 00:12:41
la source

la structure est différente dans l'exemple de Ben M . Il sera étendu au-dessus à l'intérieur de la boucle interne for qui fera qu'il ne sera pas une bonne comparaison entre les deux cas.

ce qui suit est plus précis à des fins de comparaison lorsque le code entier à vérifier (y compris la déclaration de la variable) se trouve à l'intérieur du bloc essai/capture:

        for (int j = 0; j < 10; j++)
        {
            Stopwatch w = new Stopwatch();
            w.Start();
            try { 
                double d1 = 0; 
                for (int i = 0; i < 10000000; i++) { 
                    d1 = Math.Sin(d1);
                    d1 = Math.Sin(d1); 
                } 
            }
            catch (Exception ex) {
                Console.WriteLine(ex.ToString()); 
            }
            finally { 
                //d1 = Math.Sin(d1); 
            }
            w.Stop(); 
            Console.Write("   try/catch/finally: "); 
            Console.WriteLine(w.ElapsedMilliseconds); 
            w.Reset(); 
            w.Start(); 
            double d2 = 0; 
            for (int i = 0; i < 10000000; i++) { 
                d2 = Math.Sin(d2);
                d2 = Math.Sin(d2); 
            } 
            w.Stop(); 
            Console.Write("No try/catch/finally: "); 
            Console.WriteLine(w.ElapsedMilliseconds); 
            Console.WriteLine();
        }

quand j'ai lancé le code d'essai original de Ben M , j'ai remarqué une différence à la fois dans la configuration de Debug et de Releas.

cette version, j'ai remarqué une différence dans la version de débogage (en fait plus que l'autre version), mais il n'y avait pas de différence dans la version de sortie.

conclusion :

Sur la base de ces tests, je pense que nous pouvons dire que Try/Catch does ont un petit impact sur la performance.

EDIT:

J'ai essayé d'augmenter la valeur de la boucle de 10000000 à 1000000000, et j'ai couru à nouveau dans la version pour obtenir quelques différences dans la version, et le résultat était ceci:

   try/catch/finally: 509
No try/catch/finally: 486

   try/catch/finally: 479
No try/catch/finally: 511

   try/catch/finally: 475
No try/catch/finally: 477

   try/catch/finally: 477
No try/catch/finally: 475

   try/catch/finally: 475
No try/catch/finally: 476

   try/catch/finally: 477
No try/catch/finally: 474

   try/catch/finally: 475
No try/catch/finally: 475

   try/catch/finally: 476
No try/catch/finally: 476

   try/catch/finally: 475
No try/catch/finally: 476

   try/catch/finally: 475
No try/catch/finally: 474

Vous voyez que le résultat est indifférent. Dans certains cas, la version utilisant Try/Catch est en fait plus rapide!

20
répondu awe 2009-08-25 13:40:11
la source

j'ai testé l'impact réel d'un try..catch dans une boucle serrée, et c'est trop petit pour être un souci de performances dans des situations normales.

si la boucle fait très peu de travail (dans mon test j'ai fait un x++ ), vous pouvez mesurer l'impact de la manipulation d'exception. Il a fallu environ dix fois plus de temps pour exécuter la boucle, à l'exception de la manipulation.

si la boucle fait un travail réel (dans mon test J'ai appelé le Int32.Méthode d'analyse), l' la gestion des exceptions a trop peu d'impact mesurable. J'ai eu une plus grande différence en échangeant l'ordre des boucles...

12
répondu Guffa 2009-08-21 00:32:31
la source

essayer blocs de capture ont un impact négligeable sur la performance, mais le lancement d'exception peut être assez considérable, c'est probablement là où votre collègue a été confus.

8
répondu RHicke 2009-08-21 00:29:07
la source

le try / catch a un impact sur la performance.

mais ce n'est pas un impact énorme. try/catch complexité est généralement O(1), comme un simple affectation, sauf quand ils sont placés dans une boucle. Donc, vous avez à les utiliser à bon escient.

ici est une référence sur la performance try/catch (n'explique pas la complexité de celui-ci cependant, mais il est implicite). Jetez un oeil à jeter moins D'Exceptions section

5
répondu Isaac 2011-10-19 21:18:01
la source

en théorie, un bloc essai/capture n'aura aucun effet sur le comportement du code à moins qu'une exception ne se produise. Il y a cependant de rares cas où l'existence d'un blocage "essayer/attraper" peut avoir un effet majeur, et des cas peu communs mais peu obscurs où l'effet peut être perceptible. La raison de ceci est que le code donné comme:

Action q;
double thing1()
  { double total; for (int i=0; i<1000000; i++) total+=1.0/i; return total;}
double thing2()
  { q=null; return 1.0;}
...
x=thing1();     // statement1
x=thing2(x);    // statement2
doSomething(x); // statement3

le compilateur peut être en mesure d'optimiser statement1 basé sur le fait que statement2 est garanti à exécutez avant statement3. Si le compilateur peut reconnaître que thing1 n'a pas d'effets secondaires et que thing2 n'utilise pas réellement x, il peut en toute sécurité omettre thing1 tout à fait. Si [comme dans ce cas] thing1 était coûteux, cela pourrait être une optimisation majeure, bien que les cas où thing1 est coûteux sont également ceux que le compilateur serait le moins susceptible d'optimiser. Supposons que le code ait été changé:

x=thing1();      // statement1
try
{ x=thing2(x); } // statement2
catch { q(); }
doSomething(x);  // statement3

Maintenant, il existe une séquence d'événements où statement3 pourrait exécuter sans que statement2 ait exécuté. Même si rien dans le code pour thing2 ne pouvait jeter une exception, il serait possible qu'un autre fil pourrait utiliser un Interlocked.CompareExchange pour noter que q a été effacé et l'a placé à Thread.ResetAbort , et puis effectuer un Thread.Abort() avant statement2 a écrit sa valeur à x . Puis le catch exécuterait Thread.ResetAbort() [via le délégué q ], permettant l'exécution de continuer avec la statement3. Une telle séquence des événements de bien sûr être exceptionnellement improbable, mais un compilateur est nécessaire pour générer du code qui fonctionnent selon les spécifications, même lorsque de tels événements improbables se produisent.

en général, le compilateur est beaucoup plus susceptible de remarquer des occasions de laisser de côté des bits de code simples que des bits complexes, et donc il serait rare qu'un essai/prise puisse affecter la performance beaucoup si les exceptions ne sont jamais lancées. Néanmoins, il y a des situations où l'existence d'un bloc essai/capture peut empêcher des optimisations qui, sans le try / catch,auraient permis au code de fonctionner plus rapidement.

4
répondu supercat 2014-08-28 20:11:52
la source

Voir discussion sur les try/catch mise en œuvre pour une discussion de la façon dont les blocs try/catch de travail, et comment certaines implémentations ont généraux élevés, et certains ont zéro frais généraux, lorsque aucune des exceptions se produisent. En particulier, je pense que L'implémentation de Windows 32 bits a des frais généraux élevés, et l'implémentation 64 bits n'en a pas.

3
répondu Ira Baxter 2017-05-23 14:33:26
la source

Autres questions sur