341 votes

Les blocs try/catch nuisent-ils aux performances lorsque les exceptions ne sont pas levées ?

Lors d'une revue de code avec un employé de Microsoft, nous sommes tombés sur une grande section de code à l'intérieur d'un try{} bloc. Elle et un représentant de l'informatique ont suggéré que cela peut avoir des effets sur la performance du code. En fait, ils ont suggéré que la majeure partie du code devrait se trouver en dehors des blocs try/catch, et que seules les sections importantes devraient être vérifiées. L'employé de Microsoft a ajouté et dit qu'un livre blanc à venir met en garde contre les blocs try/catch incorrects.

J'ai cherché et je l'ai trouvé peut affecter les optimisations mais il semble qu'elle ne s'applique que lorsqu'une variable est partagée entre plusieurs scopes.

Je ne m'interroge pas sur la maintenabilité du code, ni même sur le traitement des bonnes exceptions (le code en question a besoin d'être refactoré, sans aucun doute). Je ne fais pas non plus référence à l'utilisation des exceptions pour le contrôle du flux, ce qui est clairement faux dans la plupart des cas. Ce sont des questions importantes (certaines sont plus importantes), mais ce n'est pas le sujet ici.

Comment les blocs try/catch affectent-ils les performances lorsque les exceptions sont pas jeté ?

171 votes

"Celui qui sacrifie la correction à la performance ne mérite ni l'une ni l'autre."

3 votes

Joel - J'ai clairement dit que ce n'était pas le but, je ne fais pas de micro-optimisations, et un bon code est plus important pour moi, je sais mieux que cela (je suis le développeur, pas le gars de l'informatique). Il s'agit d'une question technique.

1 votes

Donc vous mangez les exceptions, et ne les jetez pas ? Traitez-vous alors le cas d'échec ou faites-vous quelque chose ?

255voto

Ben M Points 14458

Vérifiez-le.

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

Salida:

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

31 votes

Pouvez-vous également les essayer dans l'ordre inverse pour vous assurer que la compilation JIT n'a pas eu d'effet sur les premiers ?

31 votes

Les programmes de ce type ne semblent pas être de bons candidats pour tester l'impact de la gestion des exceptions, une trop grande partie de ce qui se passe dans les blocs normaux try{} catch{} va être optimisée. Je suis peut-être à côté de la plaque à ce sujet...

1 votes

+1 pour la mesure. Je me demande si un try/finally aurait les mêmes stats relatives qu'un try/catch ?

117voto

TheVillageIdiot Points 22158

Après avoir vu toutes les statistiques pour avec try/catch et sans try/catch, la curiosité m'a poussé à 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 on peut voir qu'un objet exception locale est créé sur la quatrième ligne .locals init ([0] class [mscorlib]System.Exception ex) après cela les choses sont à peu près les mêmes que pour 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 saute à la ligne IL_0025: ldloc.0 sinon nous sautons à l'étiquette IL_002d: leave.s IL_002f et les retours de fonction.

Je peux supposer sans risque de me tromper que si aucune exception ne se produit, c'est la surcharge de la création de variables locales pour contenir les objets d'exception. sólo et une instruction de saut.

48 votes

L'IL comprend un bloc try/catch dans la même notation qu'en C#, ce qui ne montre pas vraiment la surcharge que représente un try/catch en coulisses ! Le fait que l'IL n'ajoute pas grand-chose ne signifie pas qu'il n'y a pas d'ajout dans le code d'assemblage compilé. L'IL est juste une représentation commune de tous les langages .NET. Ce n'est PAS du code machine !

82voto

John Kugelman Points 108754

Non. Si les optimisations triviales qu'un bloc try/finally exclut ont un impact mesurable sur votre programme, vous ne devriez probablement pas utiliser .NET en premier lieu.

18 votes

C'est un excellent point - comparé aux autres éléments de notre liste, celui-ci devrait être minuscule. Nous devrions faire confiance aux fonctionnalités de base du langage pour se comporter correctement, et optimiser ce que nous pouvons contrôler (sql, index, algorithmes).

9 votes

Pense à des boucles serrées, mon pote. Par exemple, la boucle où vous lisez et désérialisez des objets à partir d'un flux de données de type socket dans le serveur de jeu et où vous essayez de presser autant que vous le pouvez. Donc tu utilises MessagePack pour la sérialisation des objets au lieu de binaryformatter, et tu utilises ArrayPool<byte> au lieu de créer simplement des tableaux d'octets, etc... Dans ces scénarios, quel est l'impact de multiples (peut-être imbriqués) blocs try catch dans la boucle serrée. Certaines optimisations seront sautées par le compilateur, de même que la variable d'exception va au GC Gen0. Tout ce que je dis, c'est qu'il y a "certains" scénarios où tout a un impact.

39voto

arul Points 10719

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

Rico Mariani's Performance Tidbits : Coût d'exception : Quand lancer et quand ne pas lancer

Le premier type de coût est d'avoir un traitement des exceptions dans votre code. Les exceptions gérées s'en sortent en fait relativement bien ici, je veux dire par là que le coût statique peut être beaucoup plus faible qu'en C++. Pourquoi est-ce que cela ? Eh bien, le coût statique est en fait encourus à deux endroits : Premièrement, les sites actuels de try/finally/catch/throw où il y a de la où il y a du code pour ces constructions. [ ] code non modifié, il y a la furtive coût associé au fait de garder la trace de tous les objets qui doivent être détruits dans le cas où un exception est levée. Il y a une quantité considérable de logique de nettoyage qui doit être présente et le sournois est que même le code qui n'est pas ne lève pas lui-même d'exception, n'en attrape pas ou n'en a pas n'utilise pas ouvertement les exceptions porte le fardeau de savoir comment nettoyer après lui.

Dmitriy Zaslavskiy :

Selon la note de Chris Brumme : Il y a également un certaines optimisations ne sont pas effectuées par le JIT en présence de catch

1 votes

Le truc avec le C++, c'est qu'une très grande partie de la bibliothèque standard lève des exceptions. Il n'y a rien d'optionnel à leur sujet. Vous devez concevoir vos objets avec une sorte de politique d'exception, et une fois que vous l'avez fait, il n'y a plus de coût furtif.

0 votes

Les affirmations de Rico Mariani sont complètement fausses pour le C++ natif. "Le coût statique peut être beaucoup plus faible que, par exemple, en C++". - C'est tout simplement faux. Bien que je ne sois pas sûr de ce qu'était la conception du mécanisme d'exception en 2003, lorsque l'article a été écrit. C++ vraiment n'a pas de coût du tout lorsque les exceptions sont pas lancé, peu importe le nombre de blocs try/catch que vous avez et où ils se trouvent.

1 votes

@BJovke Le "traitement des exceptions à coût zéro" du C++ signifie seulement qu'il n'y a pas de coût d'exécution lorsque les exceptions ne sont pas levées, mais il y a toujours un coût majeur de taille de code dû à tout le code de nettoyage qui appelle les destructeurs sur les exceptions. De plus, même si aucun code spécifique aux exceptions n'est généré sur le chemin de code normal, le coût n'est pas réellement nul, car la possibilité d'exceptions restreint toujours l'optimiseur (par exemple, les éléments nécessaires en cas d'exception doivent rester quelque part -> les valeurs peuvent être éliminées moins agressivement -> allocation de registre moins efficace).

29voto

awe Points 9697

La structure est différente dans l'exemple de Ben M . Il sera prolongé au-dessus de la tête à l'intérieur de la for boucle qui fera que la comparaison entre les deux cas ne sera pas bonne.

L'exemple suivant est plus précis à des fins de comparaison : l'ensemble du code à vérifier (y compris la déclaration des variables) se trouve dans le 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 original 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 fait plus que l'autre version), mais il n'y avait aucune différence dans la version Release.

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

EDITAR:
J'ai essayé d'augmenter la valeur de la boucle de 10000000 à 1000000000, et j'ai exécuté à nouveau dans Release pour obtenir quelques différences dans la version, et le résultat était 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 en fait plus rapide !

2 votes

J'ai remarqué cela aussi, parfois c'est plus rapide avec try/catch. Je l'ai commenté dans la réponse de Ben. Cependant, contrairement à 24 votants, je n'aime pas ce genre de benchmarking, je ne pense pas que ce soit une bonne indication. Le code est plus rapide dans ce cas, mais le sera-t-il toujours ?

8 votes

Cela ne prouve-t-il pas que votre machine effectuait diverses autres tâches en même temps ? Le temps écoulé n'est jamais une bonne mesure, vous devez utiliser un profileur qui enregistre le temps processeur, pas le temps écoulé.

2 votes

@Kobi : Je suis d'accord sur le fait que ce n'est pas la meilleure façon de faire du benchmarking si vous allez le publier comme une preuve que votre programme tourne plus vite qu'un autre ou autre chose, mais cela peut vous donner en tant que développeur une indication d'une méthode plus performante qu'une autre. Dans ce cas, je pense que nous pouvons dire que les différences (au moins pour la configuration Release) sont ignorables.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X