74

try/catchおよびtry/ catch/をいつ、なぜ使用するかについて、多くの質問を見てきましたfinallytryそして、 /のユースケースが確実にあることを知っていますfinally(特に、usingステートメントの実装方法であるため)。

また、try/catch と exceptions のオーバーヘッドに関する質問も見てきました。

ただし、私がリンクした質問では、最終的に試行するだけのオーバーヘッドについては触れていません。

ブロックで発生するすべての例外がないと仮定すると、ステートメントがブロックを離れるときに実行されるtryことを確認するためのオーバーヘッドはいくらですか (関数から戻ることもあります)?finallytry

繰り返しますが、私はtry/ finally、 no catch、例外のスローについてのみ尋ねています。

ありがとう!

編集:さて、私はユースケースをもう少し良く見せようとします。

どちらを使用する必要がありますDoWithTryFinallyDoWithoutTryFinally?

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

このケースは、リターン ポイントが 2 つしかないため単純化しすぎていますが、4 つ、10 つ、または 100 のリターン ポイントがあると想像してみてください。

ある時点で、次の理由でtry/を使用したいと思います。finally

  • DRY の原則を守る (特に、出口ポイントの数が増えるにつれて)
  • this.Working内部関数が例外をスローしていないことが間違っていることが判明した場合は、が に設定されていることを確認したいと思いfalseます。

したがって、パフォーマンス上の懸念、保守性、および DRY の原則を考慮して、 (特にすべての内部例外がキャッチされると仮定できるtry場合) 出口点の数に対して、 /に関連するパフォーマンスのペナルティを被りたいと仮定しfinallyますか?

編集 #2:の名前を に変更しthis.Workingましたthis.IsBusy。申し訳ありませんが、これがマルチスレッドであることを忘れていました (ただし、実際にメソッドを呼び出すスレッドは 1 つだけです)。他のスレッドは、オブジェクトがその作業を行っているかどうかを確認するためにポーリングします。戻り値は、作業が期待どおりに行われた場合の成功または失敗です。

4

6 に答える 6

101

実際に何が得られるか見てみませんか?

C# のコードの簡単なチャンクを次に示します。

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

そして、これがデバッグ ビルドでの結果の IL です。

.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
}

デバッグでの実行時に JIT によって生成されるアセンブリは次のとおりです。

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 

ここで、try と finally と return をコメント アウトすると、JIT からほぼ同じアセンブリが得られます。ここでわかる違いは、finally ブロックへのジャンプと、finally が実行された後に移動する場所を特定するためのコードです。つまり、あなたは小さな違いについて話しているのです。リリースでは、finally へのジャンプは最適化されます。中かっこは nop 命令であるため、これは次の命令へのジャンプになり、これも nop になります。これは、簡単なのぞき穴の最適化です。pop eax と jmp eax も同様に安価です。

    {
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 

つまり、try/finally のコストは非常に小さいということです。これが重要な問題領域はほとんどありません。memcpy のようなことをしていて、コピーされる各バイトの周りに try/finally を配置してから、数百 MB のデータをコピーする場合、それが問題であることがわかりますが、ほとんどの使用法では? 無視できる。

于 2010-11-05T15:23:31.200 に答える
55

したがって、オーバーヘッドがあると仮定しましょう。じゃあ使うのやめfinallyますか?うまくいけば、そうではありません。

IMO パフォーマンス メトリックは、さまざまなオプションから選択できる場合にのみ関連します。finallyを使用せずに のセマンティックを取得する方法がわかりませんfinally

于 2010-11-05T14:42:13.280 に答える
28

try/finally非常に軽量です。実際には、try/catch/finally例外がスローされない限りそうです。

少し前に試してみたクイックプロファイルアプリがありました。タイトなループでは、実行時間にまったく追加されませんでした。

もう一度投稿しますが、それは本当に簡単でした。try/catch/finallyループ内で例外をスローしない を使用して、何かを実行するタイトなループを実行し、 try/catch/finally.

于 2010-11-05T14:42:48.537 に答える
14

これに実際にいくつかのベンチマーク数値を入れてみましょう。このベンチマークが示しているのは、実際、try / finalを実行する時間は、空の関数の呼び出しのオーバーヘッドとほぼ同じくらい短いことです(ILの専門家が述べたように、「次の命令へのジャンプ」と言った方がよいでしょう。その上)。

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

結果:33,33,32,35,32 63,64,69,66,66(ミリ秒、コードの最適化がオンになっていることを確認してください)

したがって、 1,000万ループでのtry/finallyのオーバーヘッドは約33ミリ秒です。

試行ごと/最後に、0.033 / 10000000 =

3.3ナノ秒または試行/最終の33億分の1秒のオーバーヘッド。

于 2012-04-05T23:08:38.990 に答える
6

アンドリュー・バーバーが言ったこと。実際の TRY/CATCH ステートメントは、例外がスローされない限り、オーバーヘッドをまったく追加しないか、無視できる程度に追加します。最後に特別なことは何もありません。try+catch ステートメントのコードが完了した後、コードは常に finally にジャンプします

于 2010-11-05T15:17:59.373 に答える
6

低レベルでは、条件が満たされない場合finallyと同じくらい高価です。elseこれは、実際にはアセンブラー (IL) のジャンプです。

于 2010-11-05T15:23:38.017 に答える