1. ホーム
  2. c#

C#でtry/finallyのオーバーヘッド?

2023-10-20 21:07:45

質問

私たちは、いつ、なぜ try / catch そして try / catch / finally . という使用例があることも知っています。 try / finally (という方法です(特に using 文が実装されているからです)。

に関する質問も見受けられます。 try/catchと例外のオーバーヘッド .

しかし、私がリンクした質問では、JUST try-finallyを持つことのオーバーヘッドについては語られていません。

の中で起こるいかなるものからも例外が発生しないと仮定すると、そのような例外が発生する可能性はありません。 try ブロックの中で何が起きても例外は発生しないと仮定すると finally ステートメントが try ブロックから離れるときに実行されるのでしょうか?

もう一度言いますが、私が質問しているのはあくまでも try / finally いいえ catch 例外のスローはありません。

ありがとうございます!

EDITです。 さて、私の使用例をもう少しうまく示すことにします。

どちらを使うべきでしょうか。 DoWithTryFinally それとも 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;
  }
}

このケースは戻りポイントが2つしかないので単純化しすぎですが、もし4つ...10つ...100つあったらと想像してみてください。

ある時点で、私は try / finally は、以下の理由からです。

  • DRY の原則を守る (特に出口が多くなるにつれて)
  • もし内部関数が例外を投げないことについて私が間違っていることがわかったら、その時は this.Working に設定されていることを確認します。 false .

では、仮に、与えられた パフォーマンスへの懸念、保守性、および DRY 原則があります。 の場合、どのような数の出口ポイントに対して(特に私が ができる に関連するパフォーマンス・ペナルティを引き受けたいですか? try / finally ?

EDIT #2です。 の名前を変更しました。 this.Workingthis.IsBusy . すみません、これはマルチスレッドであることを言及するのを忘れていました(しかし、実際にメソッドを呼び出すのは1つのスレッドだけです)。他のスレッドはオブジェクトが仕事をしているかどうかを確認するためにポーリングしています。

どのように解決するのですか?

実際に得られるものを見てみませんか?

ここに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が実行された後にどこに行くかを決定するコードです。 つまり、ほんのわずかな違いです。 中括弧は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 ものデータのコピーを続行する場合、それが問題になることはわかりますが、ほとんどの使用法では? しかし、ほとんどの場合、それは無視できます。