315

try{}Microsoft の従業員とのコード レビュー中に、ブロック内に大きなコード セクションがあることに気付きました。彼女と IT 担当者は、これがコードのパフォーマンスに影響を与える可能性があることを示唆しました。実際、彼らはコードのほとんどを try/catch ブロックの外に置くべきであり、重要なセクションだけをチェックするべきだと提案しました。Microsoft の従業員は、次のホワイト ペーパーで間違った try/catch ブロックに対して警告を発すると付け加えました。

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

私は、コードの保守性や、適切な例外の処理について尋ねているわけではありません (問題のコードにはリファクタリングが必要であることは間違いありません)。また、フロー制御に例外を使用することについても言及していません。ほとんどの場合、これは明らかに間違っています。これらは重要な問題ですが (より重要なものもあります)、ここでは焦点を当てていません。

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

4

13 に答える 13

236

チェックしてください。

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
于 2009-08-20T20:02:06.370 に答える
114

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 の専門家ではありませんが、.locals init ([0] class [mscorlib]System.Exception ex)17 行目までは try/catch を使用しないメソッドと同じように、 4 行目でローカル例外オブジェクトが作成されていることがわかりますIL_0021: leave.s IL_002f。例外が発生した場合、コントロールは行にジャンプします。IL_0025: ldloc.0それ以外の場合は、ラベルにジャンプしIL_002d: leave.s IL_002fて関数が戻ります。

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

于 2009-08-29T21:04:23.713 に答える
73

いいえ。 try/finally ブロックが除外する些細な最適化が実際にプログラムに測定可能な影響を与える場合、そもそも .NET を使用するべきではありません。

于 2009-08-20T20:00:25.733 に答える
38

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

リコ・マリアーニのパフォーマンスのヒント:例外コスト: スローする場合とスローしない場合

最初の種類のコストは、コード内で例外処理を行うことによる静的コストです。マネージ例外は、ここでは実際に比較的うまく機能します。つまり、静的コストは C++ よりもはるかに低くなる可能性があります。どうしてこれなの?静的コストは、実際には 2 種類の場所で発生します。1 つ目は、それらの構造のコードがある実際の try/finally/catch/throw のサイトです。第 2 に、管理されていないコードでは、例外がスローされた場合に破棄する必要があるすべてのオブジェクトを追跡することに関連するステルス コストがあります。存在しなければならないかなりの量のクリーンアップ ロジックがあります。

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

Chris Brumme のメモによると、catch が存在する場合、一部の最適化が JIT によって実行されていないという事実に関連するコストもあります。

于 2009-08-20T20:00:10.197 に答える
28

Ben Mの例では、構造が異なります。内側のforループ内でオーバーヘッドが拡張されるため、2 つのケースを適切に比較できなくなります。

以下は、チェックするコード全体 (変数宣言を含む) が 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の元のテスト コードを実行したところ、デバッグ構成とリリース構成の両方に違いがあることに気付きました。

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

結論:これらのテストに基づいて、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 を使用したバージョンの方が実際には高速です。

于 2009-08-25T09:28:59.223 に答える
16

タイトなループでa の実際の影響をテストしましたtry..catchが、それ自体は小さすぎて、通常の状況でパフォーマンスの問題になることはありません。

ループがほとんど機能しない場合 (私のテストでは を実行しましたx++)、例外処理の影響を測定できます。例外処理を含むループの実行には、約 10 倍の時間がかかりました。

ループが実際の作業を行う場合 (私のテストでは Int32.Parse メソッドを呼び出しました)、例外処理の影響は小さすぎて測定できません。ループの順序を入れ替えることで、はるかに大きな違いが得られました...

于 2009-08-20T20:14:49.527 に答える
12

キャッチブロックがパフォーマンスに与える影響はごくわずかですが、例外スローはかなり大きくなる可能性があります。これはおそらく、同僚が混乱した場所です。

于 2009-08-20T20:29:07.813 に答える
7

try/catch はパフォーマンスに影響を与えます。

しかし、それは大きな影響ではありません。try/catch の複雑さは、単純な割り当てと同じように、一般に O(1) ですが、ループに配置されている場合を除きます。したがって、それらを賢く使用する必要があります。

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

于 2009-08-29T19:51:24.407 に答える
6

理論的には、例外が実際に発生しない限り、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ことに気づき、に設定し、 statement2 がその値を に書き込む前に を実行する可能性があります。次に、[デリゲートを介して] が実行され、statement3 で実行を続行できるようになります。もちろん、そのような一連のイベントは非常にありそうにありませんが、コンパイラは、そのようなありそうもないイベントが発生した場合でも、仕様に従って動作するコードを生成する必要があります。qThread.ResetAbortThread.Abort()xcatchThread.ResetAbort()q

一般に、コンパイラは、複雑なコードよりも単純なコードを除外する機会に気付く可能性がはるかに高いため、例外がスローされない場合、try/catch がパフォーマンスに大きな影響を与える可能性はほとんどありません。それでも、try/catch ブロックが存在すると最適化が妨げられる場合がありますが、try/catch がなければ、コードをより高速に実行できたはずです。

于 2014-08-28T16:11:52.880 に答える
4

try/catch ブロックがどのように機能するか、および例外が発生しない場合にオーバーヘッドが高い実装とゼロの実装があることについては、try/catch の実装に関する議論を参照してください。特に、Windows の 32 ビット実装はオーバーヘッドが高く、64 ビット実装はそうではないと思います。

于 2009-08-31T08:20:53.777 に答える