Microsoftの従業員とのコードレビュー中に、try{}
ブロック内のコードの大部分に遭遇しました。彼女とIT担当者は、これがコードのパフォーマンスに影響を与える可能性があることを提案しました。実際、彼らはコードの大部分がtry/catchブロックの外にあるべきであり、重要なセクションだけがチェックされるべきであることを示唆しました。 Microsoftの従業員は、今後のホワイトペーパーでは、不正確なtry/catchブロックに対して警告することを追加し、述べています。
私は周りを見て、それを見つけました 最適化に影響を与える可能性があります 、しかし変数がスコープ間で共有されている場合にのみ適用されるようです。
私は、コードの保守性については問いませんし、適切な例外を処理することもしません(疑わしいコードにはリファクタリングが必要です)。また、フロー制御に例外を使用することも言及していません。ほとんどの場合、これは明らかに間違っています。これらは重要な問題です(一部はより重要です)が、ここでの焦点ではありません。
Try/catchブロックは、例外がnotスローされるときにパフォーマンスにどのように影響しますか?
確認してください。
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/ finallyブロックが除外する些細な最適化が実際にプログラムに測定可能な影響を与える場合、最初に.NETを使用しないでください。
Rico Marianiのパフォーマンスに関するヒント: 例外コスト:投げるときとしないとき
最初の種類のコストは、コードで例外処理を行うことによる静的コストです。管理された例外はここでは比較的うまく機能します。つまり、静的コストはC++で言うよりもはるかに低くなります。どうしてこれなの?さて、静的コストは実際に2種類の場所で発生します。1つ目は、これらの構成要素のコードがあるtry/finally/catch/throwの実際のサイトです。次に、アンマネージコードでは、例外がスローされた場合に破棄する必要があるすべてのオブジェクトを追跡することに関連するステルスコストがあります。かなりの量のクリーンアップロジックが存在する必要があり、卑劣な部分は、それ自体が例外をスローしたりキャッチしたり、明示的に例外を使用したりしないコードであっても、それ自体をクリーンアップする方法を知る負担を負っていることです。
ドミトリー・ザスラフスキー:
Chris Brummeのメモによると、キャッチの存在下ではJITによって最適化が実行されないという事実に関連するコストもあります。
この例の構造は、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から元のテストコードを実行すると、DebugとReleasの両方の構成に違いがあることに気付きました。
このバージョンでは、デバッグバージョンの違い(実際には他のバージョンよりも大きい)に気付きましたが、リリースバージョンでは違いはありませんでした。
Conclution:
これらのテストに基づいて、Try/Catch doesはパフォーマンスにわずかな影響を与えると言えます。
編集:
ループ値を10000000から1000000000に増やして、Releaseで再度実行して、リリースのいくつかの違いを取得したところ、結果は次のようになりました。
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を使用したバージョンの方が実際に高速です!
try..catch
の実際の影響をタイトループでテストしましたが、通常の状況ではパフォーマンスの問題にはなりません。
ループがほとんど機能しない場合(私のテストではx++
を実行しました)、例外処理の影響を測定できます。例外処理を伴うループの実行には、約10倍時間がかかりました。
ループが実際の作業を行う場合(私のテストでは、Int32.Parseメソッドを呼び出しました)、例外処理の影響はあまり測定できません。ループの順序を入れ替えることで、はるかに大きな違いが得られました...
try catchブロックはパフォーマンスにほとんど影響を与えませんが、例外のスローはかなり大きくなる可能性があります。これはおそらく同僚が混乱した場所です。
Try/catchはパフォーマンスに影響します。
しかし、それは大きな影響ではありません。 try/catchの複雑さは通常、単純な割り当てと同様に、ループに配置される場合を除き、O(1)です。したがって、それらを賢く使用する必要があります。
ここ はtry/catchのパフォーマンスに関するリファレンスです(ただし、その複雑さについては説明しませんが、暗示されています)。 Throw Fewer Exceptionsセクションをご覧ください
理論的には、実際に例外が発生しない限り、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()
を実行することができますstatement2はその値をx
に書き込みました。次に、catch
はThread.ResetAbort()
[デリゲートq
]を実行し、statement3で実行を継続できます。もちろん、このようなイベントのシーケンスは非常にまれですが、コンパイラは、このようなありそうもないイベントが発生した場合でも仕様に従って動作するコードを生成する必要があります。
一般に、コンパイラは複雑なコードよりも単純なコードを省く機会に気付く可能性が高いため、例外がスローされない場合、try/catchがパフォーマンスに大きな影響を与えることはまれです。それでも、try/catchブロックが存在すると最適化が妨げられる場合がありますが、try/catchの場合は、コードの実行が高速になります。
Try/catchブロックがどのように機能するか、および例外が発生しない場合に一部の実装が高いオーバーヘッドを持ち、一部のオーバーヘッドがゼロになる方法については、 try/catch実装に関する議論 を参照してください。特に、Windows 32ビット実装には高いオーバーヘッドがあり、64ビット実装にはないと思います。