71 votes

La surcharge de try/finally en C# ?

Nous avons vu de nombreuses questions sur quand et pourquoi utiliser try / catch et try / catch / finally . Et je sais qu'il y a définitivement un cas d'utilisation pour try / finally (d'autant plus que c'est la façon dont le using est mise en œuvre).

Nous avons également vu des questions sur la surcharge de try/catch et des exceptions .

La question à laquelle j'ai fait référence ne parle cependant pas des frais généraux liés à la présence de JUST try-finally.

En supposant qu'il n'y a pas d'exception à tout ce qui se passe dans le cadre de la try quel est l'intérêt de s'assurer que les finally sont exécutées lorsque l'on quitte le try (parfois en revenant de la fonction) ?

Encore une fois, je demande UNIQUEMENT try / finally non catch pas de levée d'exceptions.

Merci !

EDIT : Ok, je vais essayer de montrer un peu mieux mon cas d'utilisation.

Lequel dois-je utiliser ? DoWithTryFinally ou DoWithoutTryFinally ?

public bool DoWithTryFinally()
{
  this.IsBusy = true;

  try
  {
    if (DoLongCheckThatWillNotThrowException())
    {
      this.DebugLogSuccess();
      return true;
    }
    else
    {
      this.ErrorLogFailure();
      return false;
    }
  }
  finally
  {
    this.IsBusy = false;
  }
}

public bool DoWithoutTryFinally()
{
  this.IsBusy = true;

  if (DoLongCheckThatWillNotThrowException())
  {
    this.DebugLogSuccess();

    this.IsBusy = false;
    return true;
  }
  else
  {
    this.ErrorLogFailure();

    this.IsBusy = false;
    return false;
  }
}

Ce cas est excessivement simpliste car il n'y a que deux points de retour, mais imaginez s'il y en avait quatre... ou dix... ou cent.

A un moment donné, je voudrais utiliser try / finally pour les raisons suivantes :

  • Respecter les principes du DRY (surtout lorsque le nombre de points de sortie augmente).
  • S'il s'avère que je me trompe en disant que ma fonction interne ne lève pas d'exception, alors je veux m'assurer que this.Working est réglé sur false .

Donc hypothétiquement, étant donné les problèmes de performance, la maintenabilité et les principes DRY, pour quel nombre de points de sortie (surtout si je peut en supposant que toutes les exceptions internes sont rattrapées), est-ce que je veux subir une quelconque pénalité de performance associée à try / finally ?

EDIT #2 : J'ai changé le nom de this.Working à this.IsBusy . Désolé, j'ai oublié de préciser qu'il s'agit d'une méthode multithread (bien qu'un seul thread n'appelle jamais la méthode) ; les autres threads s'interrogent pour savoir si l'objet effectue son travail. La valeur de retour est simplement un succès ou un échec pour savoir si le travail s'est déroulé comme prévu.

95voto

plinth Points 26817

Pourquoi ne pas regarder ce que vous obtenez réellement ?

Voici un simple morceau de code en C# :

    static void Main(string[] args)
    {
        int i = 0;
        try
        {
            i = 1;
            Console.WriteLine(i);
            return;
        }
        finally
        {
            Console.WriteLine("finally.");
        }
    }

Et voici le résultat de l'IL dans la version de débogage :

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 1
    .locals init ([0] int32 i)
    L_0000: nop 
    L_0001: ldc.i4.0 
    L_0002: stloc.0 
    L_0003: nop 
    L_0004: ldc.i4.1 
    L_0005: stloc.0 
    L_0006: ldloc.0 // here's the WriteLine of i 
    L_0007: call void [mscorlib]System.Console::WriteLine(int32)
    L_000c: nop 
    L_000d: leave.s L_001d // this is the flavor of branch that triggers finally
    L_000f: nop 
    L_0010: ldstr "finally."
    L_0015: call void [mscorlib]System.Console::WriteLine(string)
    L_001a: nop 
    L_001b: nop 
    L_001c: endfinally 
    L_001d: nop 
    L_001e: ret 
    .try L_0003 to L_000f finally handler L_000f to L_001d
}

et voici l'assemblage généré par le JIT lors de l'exécution en débogage :

00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        edi 
00000004  push        esi 
00000005  push        ebx 
00000006  sub         esp,34h 
00000009  mov         esi,ecx 
0000000b  lea         edi,[ebp-38h] 
0000000e  mov         ecx,0Bh 
00000013  xor         eax,eax 
00000015  rep stos    dword ptr es:[edi] 
00000017  mov         ecx,esi 
00000019  xor         eax,eax 
0000001b  mov         dword ptr [ebp-1Ch],eax 
0000001e  mov         dword ptr [ebp-3Ch],ecx 
00000021  cmp         dword ptr ds:[00288D34h],0 
00000028  je          0000002F 
0000002a  call        59439E21 
0000002f  xor         edx,edx 
00000031  mov         dword ptr [ebp-40h],edx 
00000034  nop 
        int i = 0;
00000035  xor         edx,edx 
00000037  mov         dword ptr [ebp-40h],edx 
        try
        {
0000003a  nop 
            i = 1;
0000003b  mov         dword ptr [ebp-40h],1 
            Console.WriteLine(i);
00000042  mov         ecx,dword ptr [ebp-40h] 
00000045  call        58DB2EA0 
0000004a  nop 
            return;
0000004b  nop 
0000004c  mov         dword ptr [ebp-20h],0 
00000053  mov         dword ptr [ebp-1Ch],0FCh 
0000005a  push        4E1584h 
0000005f  jmp         00000061 
        }
        finally
        {
00000061  nop 
            Console.WriteLine("finally.");
00000062  mov         ecx,dword ptr ds:[036E2088h] 
00000068  call        58DB2DB4 
0000006d  nop 
        }
0000006e  nop 
0000006f  pop         eax 
00000070  jmp         eax 
00000072  nop 
    }
00000073  nop 
00000074  lea         esp,[ebp-0Ch] 
00000077  pop         ebx 
00000078  pop         esi 
00000079  pop         edi 
0000007a  pop         ebp 
0000007b  ret 
0000007c  mov         dword ptr [ebp-1Ch],0 
00000083  jmp         00000072 

Maintenant, si je commente les try et finally et le return, j'obtiens un assemblage presque identique du JIT. Les différences que vous verrez sont un saut dans le bloc finally et du code pour savoir où aller après l'exécution du finally. Il s'agit donc de minuscules différences. Dans la release, le saut dans le finally sera optimisé - les accolades sont des instructions nop, donc cela deviendra un saut vers l'instruction suivante, qui est aussi un nop - c'est une optimisation facile. Le pop eax et ensuite jmp eax est également bon marché.

    {
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        edi 
00000004  push        esi 
00000005  push        ebx 
00000006  sub         esp,34h 
00000009  mov         esi,ecx 
0000000b  lea         edi,[ebp-38h] 
0000000e  mov         ecx,0Bh 
00000013  xor         eax,eax 
00000015  rep stos    dword ptr es:[edi] 
00000017  mov         ecx,esi 
00000019  xor         eax,eax 
0000001b  mov         dword ptr [ebp-1Ch],eax 
0000001e  mov         dword ptr [ebp-3Ch],ecx 
00000021  cmp         dword ptr ds:[00198D34h],0 
00000028  je          0000002F 
0000002a  call        59549E21 
0000002f  xor         edx,edx 
00000031  mov         dword ptr [ebp-40h],edx 
00000034  nop 
        int i = 0;
00000035  xor         edx,edx 
00000037  mov         dword ptr [ebp-40h],edx 
        //try
        //{
            i = 1;
0000003a  mov         dword ptr [ebp-40h],1 
            Console.WriteLine(i);
00000041  mov         ecx,dword ptr [ebp-40h] 
00000044  call        58EC2EA0 
00000049  nop 
        //    return;
        //}
        //finally
        //{
            Console.WriteLine("finally.");
0000004a  mov         ecx,dword ptr ds:[034C2088h] 
00000050  call        58EC2DB4 
00000055  nop 
        //}
    }
00000056  nop 
00000057  lea         esp,[ebp-0Ch] 
0000005a  pop         ebx 
0000005b  pop         esi 
0000005c  pop         edi 
0000005d  pop         ebp 
0000005e  ret 

Il s'agit donc de coûts très, très faibles pour essayer/finir. Il y a très peu de domaines où cela a de l'importance. Si vous faites quelque chose comme memcpy et que vous mettez un try/finally autour de chaque octet copié et que vous procédez ensuite à la copie de centaines de Mo de données, je pourrais voir que c'est un problème, mais dans la plupart des utilisations ? Négligeable.

53voto

Brian Rasmussen Points 68853

Supposons donc qu'il y ait des frais généraux. Allez-vous arrêter d'utiliser finally alors ? J'espère que non.

Les mesures de performance ne sont pertinentes que si vous pouvez choisir entre différentes options. Je ne vois pas comment vous pouvez obtenir la sémantique de finally sans utiliser finally .

28voto

Andrew Barber Points 25990

try/finally est très léger. En fait, c'est aussi le cas try/catch/finally tant qu'aucune exception n'est levée.

J'ai fait une application de profilage rapide il y a quelque temps pour la tester ; dans une boucle serrée, elle n'a vraiment rien ajouté au temps d'exécution.

Je le posterais encore une fois, mais c'était vraiment simple ; il suffit de faire tourner une boucle serrée faisant quelque chose, avec un try/catch/finally qui ne lève aucune exception à l'intérieur de la boucle, et comparez les résultats avec une version sans l'option try/catch/finally .

11voto

Nicholas Petersen Points 832

Mettons des chiffres de référence sur cette question. Ce que ce benchmark montre, c'est qu'en effet, le temps d'un try/finally est à peu près aussi faible que l'overhead d'un appel à une fonction vide (ou plutôt : "un saut à l'instruction suivante" comme l'a dit l'expert IL ci-dessus).

            static void RunTryFinallyTest()
            {
                int cnt = 10000000;

                Console.WriteLine(TryFinallyBenchmarker(cnt, false));
                Console.WriteLine(TryFinallyBenchmarker(cnt, false));
                Console.WriteLine(TryFinallyBenchmarker(cnt, false));
                Console.WriteLine(TryFinallyBenchmarker(cnt, false));
                Console.WriteLine(TryFinallyBenchmarker(cnt, false));

                Console.WriteLine(TryFinallyBenchmarker(cnt, true));
                Console.WriteLine(TryFinallyBenchmarker(cnt, true));
                Console.WriteLine(TryFinallyBenchmarker(cnt, true));
                Console.WriteLine(TryFinallyBenchmarker(cnt, true));
                Console.WriteLine(TryFinallyBenchmarker(cnt, true));

                Console.ReadKey();
            }

            static double TryFinallyBenchmarker(int count, bool useTryFinally)
            {
                int over1 = count + 1;
                int over2 = count + 2;

                if (!useTryFinally)
                {
                    var sw = Stopwatch.StartNew();
                    for (int i = 0; i < count; i++)
                    {
                        // do something so optimization doesn't ignore whole loop. 
                        if (i == over1) throw new Exception();
                        if (i == over2) throw new Exception();
                    }
                    return sw.Elapsed.TotalMilliseconds;
                }
                else
                {
                    var sw = Stopwatch.StartNew();
                    for (int i = 0; i < count; i++)
                    {
                        // do same things, just second in the finally, make sure finally is 
                        // actually doing something and not optimized out
                        try
                        {
                            if (i == over1) throw new Exception();
                        } finally
                        {
                            if (i == over2) throw new Exception();
                        }
                    }
                    return sw.Elapsed.TotalMilliseconds;
                }
            }

Résultat : 33,33,32,35,32 63,64,69,66,66 (millisecondes, assurez-vous d'avoir activé l'optimisation du code)

Donc environ 33 millisecondes de surcharge pour le try/finally dans 10 millions boucles.

Par essai/finalement alors, nous parlons de 0.033/10000000 =

3,3 nanosecondes ou 3,3 milliardièmes de seconde de surcharge d'un try/finally.

6voto

Liviu M. Points 3872

Aux niveaux inférieurs finally est tout aussi coûteux qu'un else si la condition n'est pas remplie. Il s'agit en fait d'un saut en assembleur (IL).

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