web-dev-qa-db-fra.com

Les blocs try / catch affectent-ils les performances lorsque des exceptions ne sont pas lancées?

Lors d'une révision de code avec un employé de Microsoft, nous avons découvert une grande section de code dans un try{} bloquer. Elle et un représentant informatique ont suggéré que cela pouvait avoir des effets sur les performances du code. En fait, ils ont suggéré que la majeure partie du code soit en dehors des blocs try/catch et que seules les sections importantes soient vérifiées. L'employé de Microsoft a ajouté et a déclaré qu'un livre blanc à venir met en garde contre les blocages try/catch incorrects.

J'ai regardé autour de moi et l'ai trouvé peut affecter les optimisations , mais cela ne semble s'appliquer que lorsqu'une variable est partagée entre des étendues.

Je ne parle pas de la maintenabilité du code, ni même de la gestion des exceptions appropriées (le code en question doit sans doute être remanié, etc.). Je ne fais pas non plus allusion à l'utilisation d'exceptions pour le contrôle de flux, cela est clairement faux dans la plupart des cas. Ce sont des questions importantes (certaines sont plus importantes), mais pas l’objectif ici.

Comment les blocs try/catch affectent-ils les performances lorsque des exceptions sont et non pas ?

250
Kobi

Vérifie ça.

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
189
Ben M

Après avoir vu toutes les statistiques pour avec try/catch et sans try/catch, la curiosité m'a obligé à 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 local est créé sur la quatrième ligne .locals init ([0] class [mscorlib]System.Exception ex) après que les choses soient assez identiques à celles de la méthode sans try/catch jusqu'à la ligne dix-sept IL_0021: leave.s IL_002f. Si une exception se produit, le contrôle passe à la ligne IL_0025: ldloc.0. Sinon, nous passons à l'étiquette IL_002d: leave.s IL_002f Et la fonction revient.

Je peux sans risque supposer que si aucune exception ne se produit, il s’agit de la création de variables locales pour contenir des objets d’exception. seulement et une instruction de saut.

99
TheVillageIdiot

Non. 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 au départ.

60
John Kugelman

Explication assez complète du modèle d'exception .NET.

Tidbits de performance de Rico Mariani: Coût d'exception: quand lancer et quand ne pas le faire

Le premier type de coût est le coût statique de la gestion des exceptions dans votre code. Les exceptions gérées fonctionnent relativement bien ici, ce qui signifie que le coût statique peut être beaucoup moins élevé qu'en C++. Pourquoi est-ce? Eh bien, le coût statique est réellement lié à deux types d’endroits: d’abord, les sites réels de try/finally/catch/throw où il y a du code pour ces constructions. Deuxièmement, dans le code non incorporé, il y a le coût furtif associé au suivi de tous les objets qui doivent être détruits dans le cas où une exception est levée. Il doit y avoir une quantité considérable de logique de nettoyage à faire et la partie sournoise est que même le code qui ne lance pas ou capture lui-même ou utilise autrement des exceptions n’a toujours pas 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é au fait que certaines optimisations ne sont pas effectuées par JIT en présence de capture

33
arul

La structure est différente dans l'exemple de Ben M. Il sera étendu dans la boucle interne for, ce qui empêchera la comparaison entre les deux cas.

Ce qui suit est plus précis pour la comparaison où tout le code à vérifier (y compris la déclaration de variable) se trouve à l'intérieur du bloc Try/Catch:

        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();
        }

Lorsque j'ai exécuté le code de test d'origine à partir de Ben M, j'ai remarqué une différence dans la configuration Debug et Releas.

Cette version, j'ai remarqué une différence dans la version de débogage (en réalité plus que l'autre version), mais ce n'était pas différent dans la version Release.

Conclusion :
Sur la base de ces tests, je pense que nous pouvons dire que Try/Catch fait a un faible impact sur les performances.

EDIT:
J'ai essayé d’augmenter la valeur de la boucle de 10000000 à 1000000000 et j’ai ré-exécuté dans Release pour obtenir quelques différences dans la version. Le résultat est le suivant:

   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 sans conséquence. Dans certains cas, la version utilisant Try/Catch est réellement plus rapide!

23
awe

J'ai testé l'impact réel d'un try..catch dans une boucle serrée, et il est trop petit en soi pour être un problème de performance dans une situation normale.

Si la boucle fait très peu de travail (lors de mon test, j'ai fait un x++), vous pouvez mesurer l’impact de la gestion des exceptions. La boucle avec gestion des exceptions a pris environ dix fois plus de temps.

Si la boucle effectue un travail réel (dans mon test, j'ai appelé la méthode Int32.Parse), la gestion des exceptions a trop peu d'impact pour être mesurable. J'ai eu une différence beaucoup plus grande en inversant l'ordre des boucles ...

14
Guffa

les blocs catch ont un impact négligeable sur les performances, mais une exception peut être assez importante. C'est probablement là que votre collègue était dérouté.

10
RHicke

L'essai/attrape HAS impact sur la performance.

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

Here est une référence sur les performances try/catch (n’explique pas la complexité de celle-ci, mais elle est implicite). Jetez un coup d'oeil à la section Jetez moins d'exceptions

7
Isaac

En théorie, un bloc try/catch n'aura aucun effet sur le comportement du code, à moins qu'une exception ne se produise réellement. Il existe cependant de rares circonstances où l’existence d’un bloc try/catch peut avoir un effet majeur, et d’autres rares, mais à peine obscures, dans lesquelles l’effet peut être perceptible. La raison en est que le code donné, tel que:

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 peut-être optimiser statement1 en se basant sur le fait que l'instruction2 est exécutée avant l'instruction3. 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. Si [comme dans ce cas] chose1 était coûteux, cela pourrait être une optimisation majeure, bien que les cas où chose1 soit coûteux soient également ceux que le compilateur serait le moins susceptible d'optimiser. Supposons que le code a é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ù instruction3 pourrait s'exécuter sans que l'instruction2 soit exécutée. Même si rien dans le code de thing2 Ne peut lever une exception, il serait possible qu'un autre thread utilise un Interlocked.CompareExchange Pour remarquer que q a été effacé et le définir sur Thread.ResetAbort, Puis effectuez une Thread.Abort() avant que l'instruction2 écrive sa valeur dans x. Ensuite, le catch exécuterait Thread.ResetAbort() [via délégué q], permettant ainsi à l'exécution de continuer avec statement3. Une telle séquence d'événements serait bien sûr exceptionnellement improbable, mais un compilateur est requis pour générer un code fonctionnant conformément aux 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 possibilités d'omettre de simples morceaux de code que des codes complexes. Il serait donc rare qu'un essai/capture puisse affecter considérablement les performances si les exceptions ne sont jamais levées. Néanmoins, il existe des situations où l’existence d’un bloc try/catch peut empêcher des optimisations qui - sans le try/catch - auraient permis au code de s’exécuter plus rapidement.

4
supercat

Voir discussion sur l'implémentation try/catch pour une discussion sur le fonctionnement des blocs try/catch et sur le coût élevé de certaines implémentations, voire nul, lorsqu'aucune exception ne se produit. En particulier, je pense que l'implémentation Windows 32 bits a une surcharge, contrairement à l'implémentation 64 bits.

3
Ira Baxter