c# - 戻り値 - try/catchブロックは、例外がスローされないときにパフォーマンスを低下させますか?




例外 戻り値 (7)

try / catchとtry / catchなしのすべての統計を確認した後、好奇心は、両方のケースで何が生成されているかを見るため私に後ろを向かわせました。 ここにコードです:

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

私はILのエキスパートではありませんが、ローカルの例外オブジェクトは、4行目の.locals init ([0] class [mscorlib]System.Exception ex) IL_0021: leave.s IL_002f 。 例外が発生した場合、コントロールはIL_0025: ldloc.0行にジャンプしIL_0025: ldloc.0そうでなければ、 IL_002d: leave.s IL_002fラベルにジャンプしIL_002d: leave.s IL_002fと関数が戻ります。

例外が発生しなければ、例外オブジェクトとジャンプ命令を保持するためのローカル変数を作成するオーバーヘッドであると、私は安全に仮定できます。

マイクロソフトの従業員のコードレビューでは、 try{}ブロック内に大きなコードセクションがあります。 彼女とIT担当者は、これがコードのパフォーマンスに影響を与える可能性があると示唆しました。 実際、彼らはコードの大部分がtry / catchブロックの外側にあるべきであり、重要なセクションだけがチェックされるべきだと提案しました。 マイクロソフト社の従業員は追加され、今後のホワイトペーパーで不正なtry / catchブロックに対する警告が出されると述べた。

私は周りを見回し、 最適化影響する可能性があることを発見しました 、スコープ間で変数が共有されている場合にのみ適用されるようです。

私はコードの保守性について質問していないし、正しい例外を処理することさえしていない(問題のコードは再考の必要があり、間違いない)。 私はまた、フロー制御の例外を使用することに言及していない、これはほとんどの場合明らかに間違っています。 これらは重要な問題ですが(重要なものもあります)、ここでは焦点を当てません。

try / catchブロックは、例外がスローされない場合のパフォーマンスにどのように影響しますか?

編集:私は賞金を追加しています。 興味深い回答がありますが、私はもう少し詳しく説明したいと思います。


tryキャッチブロックはパフォーマンスにはほとんど影響を与えませんが、例外はかなり大きなものになります。これはおそらくあなたの同僚が混乱していた場所です。


.NET例外モデルの包括的な説明。

Rico Marianiのパフォーマンスのちょっとしたヒント: 例外コスト:投げるときとしないとき

最初の種類のコストは、コード内での例外処理の静的コストです。 管理された例外は実際には比較的よくうまくいきます。静的コストはC ++で言うよりはるかに低くなる可能性があります。 どうしてこれなの? まあ、静的コストは本当に2つの種類の場所で発生します:まず、try / finally / catch / throwの実際のサイトで、それらの構造のコードがあります。 第2に、無人コードでは、例外がスローされた場合に破壊されなければならないすべてのオブジェクトの追跡に関連するステルスコストがあります。 かなりの量のクリーンアップロジックが存在しなければならず、不正な部分は、それ自体がスローまたはキャッチしないか、またはそれ以外の方法で明示的に使用されていないコードでさえも、それ自体後にクリーンアップする方法を知っている負担を依然として負うということです。

ドミトリー・ザスラフスキー:

Chris Brumme氏の注記:キャッチの存在下でJITによって実行されていない最適化があるという事実に関連したコストもあります


Ben Mの例では、構造が異なります。 2つの場合の比較が良くない原因となる内部forループ内forオーバーヘッドが拡張されます。

以下は、チェックするコード全体(変数宣言を含む)が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();
        }

Ben Mのオリジナルのテストコードを実行したとき、DebugとReleasの両方の設定に違いがあることがわかりました。

このバージョンでは、デバッグバージョン(実際には他のバージョンよりも)の違いに気づきましたが、リリースバージョンに違いはありませんでした。

結論
これらのテストに基づいて、Try / Catchのパフォーマンスへの影響は小さいと言えるでしょう。

編集:
ループの値を10000000から1000000000に増やそうとしましたが、リリースでいくつかの違いを得るためにリリースで再度実行されました。結果は次のとおりです。

   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

あなたは結果が不公平であることを確認します。 場合によっては、Try / Catchを使用したバージョンが実際には高速です!


それを確認してください。

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

出力:

00:00:00.4269033  // with try/catch
00:00:00.4260383  // without.

ミリ秒単位で:

449
416

新しいコード:

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

新しい結果:

   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

理論的には、実際に例外が発生しない限り、try / catchブロックはコードの動作に影響を与えません。 しかし、try / catchブロックの存在が大きな影響を及ぼす可能性のあるまれな状況や、その効果が顕著になる可能性のある珍しいものもあります。 その理由は、次のようなコードがあります。

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

コンパイラは、statement2がstatement3より前に実行されることが保証されているという事実に基づいてstatement1を最適化することができます。 コンパイラがthing1に副作用がなく、thing2が実際にxを使わないことを認識できれば、thing1を完全に省略することができます。 thing1が高価な場合は、コンパイラが最適化する可能性が最も低いケースもありますが、[この場合は] thing1が高価だった場合、それは大きな最適化になる可能性があります。 コードが変更されたとします。

x=thing1();      // statement1
try
{ x=thing2(x); } // statement2
catch { q(); }
doSomething(x);  // statement3

これで、statement2が実行されずにstatement3が実行できる一連のイベントが存在します。 thing2のコードに何も例外がスローされても、別のスレッドがInterlocked.CompareExchangeを使用してqがクリアされ、 Thread.ResetAbortに設定された後、 Thread.Abort()実行するThread.Abort()ますstatement2はその値をx書きました。 その後、 catchThread.ResetAbort() [delegate qを介して]実行し、statement3で実行を続行します。 そのような事象のシーケンスはもちろん例外的にはあり得ませんが、たとえそうした不可能な事象が発生したとしても、仕様に従って動作するコードをコンパイラが生成する必要があります。

一般に、コンパイラは、複雑なものよりも単純なコードを除外する機会を気付く可能性が非常に高いため、例外が投げられることがなければ、try / catchがパフォーマンスに多大な影響を与えることはまれです。 それでも、try / catchブロックの存在がtry-catchの最適化を妨げて、コードをより速く実行できるようにしている状況がいくつかあります。


試行錯誤はパフォーマンスにHASの影響を与えます。

しかし、その巨大な影響はありません。 try / catch複雑さは一般的に単純な代入のように、ループ内に置かれる場合を除いてO(1)です。 だからあなたはそれらを賢明に使う必要があります。

Hereでtry / catchのパフォーマンスについてのリファレンスです(ただし、その複雑さは説明されていませんが、それは暗に示されています)。 より少ない例外投げるセクションを見てください。





try-catch