C#でのtry / catchの実際のオーバーヘッドは何ですか?


94

つまり、try / catchはオーバーヘッドを追加するため、プロセスフローを制御する良い方法ではないことを知っていますが、このオーバーヘッドはどこから発生し、実際にどのような影響がありますか?

回答:


52

私は言語実装の専門家ではありません(そのため、これを簡単に理解してください)が、最大のコストの1つは、スタックをほどいて、スタックトレース用に保存することです。私はこれが例外がスローされたときにのみ起こると思います(しかし、私は知りません)、そうなら、これは例外がスローされるたびにまともなサイズの隠しコストになります...別のコードでは、多くのことが起こっています。

EXCEPTIONAL動作の例外を使用している限り、問題はないと思います(そのため、プログラムを通過する通常の予想されるパスではありません)。


33
より正確には、試してみるのが安く、キャッチは安く、投げるのは高価です。試行錯誤を避けた場合、スローは依然として高価です。
Windowsプログラマ

4
うーん-マークアップはコメントでは機能しません。もう一度やり直してくださいするには-例外ではない「異例の行動」や条件のために、エラーのためのものである:blogs.msdn.com/kcwalina/archive/2008/07/17/...
HTTP 410

2
@Windowsプログラマーの統計/ソースしてください?
Kapé

100

ここで3つのポイントを作成します。

  • まず、コードに実際にtry-catchブロックを含めることによるパフォーマンスの低下はほとんどまたはまったくありません。これをアプリケーションで使用しないようにする場合は、考慮すべきではありません。パフォーマンスヒットは、例外がスローされたときにのみ効果を発揮します。

  • 他の人が述べたスタックの巻き戻し操作などに加えて例外がスローされると、スタックトレースなどの例外クラスのメンバーにデータを入力するために、ランタイム/リフレクションに関連する一連の事柄が発生することに注意してください。オブジェクトやさまざまな型メンバーなど

  • これは、例外を再スローする場合の一般的なアドバイスがthrow;、例外を再度スローしたり、新しい例外を構築したりするのではなく、スタック情報がすべて収集されるのと同じ理由の1つだと思います。それがすべて保存されているスロー。


41
また、「throw ex」として例外を再スローすると、元のスタックトレースが失われ、CURRENTスタックトレースに置き換えられます。めったに欲しいものではありません。単に「スロー」すると、例外の元のスタックトレースが保持されます。
Eddie、

@Eddie Orthrow new Exception("Wrapping layer’s error", ex);
binki 2018

21

例外がスローされないときにtry / catch / finallyを使用するオーバーヘッド、または例外を使用してプロセスフローを制御するオーバーヘッドについて尋ねていますか?後者は、ダイナマイトの棒を使用して幼児の誕生日のろうそくを照らすのにいくらか似ており、関連するオーバーヘッドは次の領域に分類されます。

  • 通常はキャッシュにない常駐データにアクセスする例外がスローされるため、追加のキャッシュミスが予想されます。
  • 通常はアプリケーションのワーキングセットにない非常駐コードおよびデータにアクセスする例外がスローされるため、追加のページフォールトが予想されます。

    • たとえば、例外をスローするには、CLRが現在のIPおよび例外が処理されるまでのすべてのフレームの戻りIPとフィルターブロックに基づいて、finallyブロックとcatchブロックの場所を見つける必要があります。
    • メタデータの読み取りなど、診断目的でフレームを作成するための追加の建設費と名前解決
    • 上記の両方の項目は、通常「コールド」コードとデータにアクセスするため、メモリに負荷がかかっている場合は、ハードページフォールトが発生する可能性があります。

      • CLRは、ローカリティを改善するために頻繁に使用されるデータから遠く離れて使用されるコードとデータを配置しようとします。そのため、寒さを強制的に高温にするため、これは効果がありません。
      • ハードページフォールトのコストがあれば、それ以外のものはすべて小さくなります。
  • 通常のキャッチ状況は深いことが多いため、上記の影響は拡大する傾向があります(ページフォールトの可能性が高くなります)。

コストの実際の影響に関しては、これはその時点でコードで他に何が起こっているかによって大きく異なる可能性があります。Jon Skeetはここ良い要約を持っています、そしていくつかの役に立つリンクがあります。例外がパフォーマンスを著しく損なうところまでたどり着くと、パフォーマンスだけでなく例外の使用に関しても問題が発生するという彼の発言に同意する傾向があります。


6

私の経験では、最大のオーバーヘッドは、実際に例外をスローしてそれを処理することです。私はかつて、次のようなコードを使用して、誰かがオブジェクトを編集する権利を持っているかどうかを確認するプロジェクトに取り組みました。このHasRight()メソッドは、プレゼンテーションレイヤーのあらゆる場所で使用され、多くの場合、数百のオブジェクトに対して呼び出されました。

bool HasRight(string rightName, DomainObject obj) {
  try {
    CheckRight(rightName, obj);
    return true;
  }
  catch (Exception ex) {
    return false;
  }
}

void CheckRight(string rightName, DomainObject obj) {
  if (!_user.Rights.Contains(rightName))
    throw new Exception();
}

テストデータベースがテストデータでいっぱいになると、新しいフォームを開くときなどに、非常に目に見える速度低下が発生します。

だから私はそれを次のようにリファクタリングしました-それは-後のクイック 'nダーティ測定によれば-約2桁速い

bool HasRight(string rightName, DomainObject obj) {
  return _user.Rights.Contains(rightName);
}

void CheckRight(string rightName, DomainObject obj) {
  if (!HasRight(rightName, obj))
    throw new Exception();
}

つまり、通常のプロセスフローで例外を使用すると、例外なしで同様のプロセスフローを使用するよりも約2桁遅くなります。


1
なぜここで例外をスローしたいのですか?その場で権利がない場合にも対応できます。
ThunderGr 2013

@ThunderGrは実際に私が変更したもので、2桁速くなっています。
トビ

5

一般に受け入れられている理論に反して、try/ catchはパフォーマンスに大きな影響を与える可能性があり、それは例外がスローされるかどうかです!

  1. デバッグ支援から期待できるように、一部の自動最適化(設計による)を無効にし、場合によってはデバッグコードを挿入します。この点で私に反対する人は常にいますが、言語はそれを要求し、逆アセンブリはそれを示しているので、それらの人は辞書の定義によって妄想的です。
  2. メンテナンスに悪影響を及ぼす可能性があります。これは実際にはここで最も重要な問題ですが、(ほぼ完全に焦点を当てた)私の最後の回答が削除されたので、より重要な問題(ではなく、重要度の低い問題(マイクロ最適化)に焦点を当てようとします(マクロ最適化)。

前者は、長年にわたって、マイクロソフトのMVPによるブログの記事のカップルでカバーされている、と私はあなたがそれらを簡単に見つけることができる信頼まだStackOverflowのは気にそんなにについての内容私のようにそれらのいくつかにリンクを提供しますので、フィラーの証拠:

/ を使用した場合と使用しない場合の逆アセンブルされたコードの違いを示すこの回答もあります。trycatch

コード生成で露骨に観察できるオーバーヘッドがあること明らかであり、そのオーバーヘッドはMicrosoftの価値を認める人々によって認められているように見えます!それでも私はインターネットを繰り返しています ...

はい、わずかなコード行に対して数十の追加のMSIL命令があり、無効にされた最適化さえカバーしていないので、技術的にはマイクロ最適化です。


私は数年前に回答を投稿しましたが、プログラマーの生産性(マクロ最適化)に焦点を合わせたため、削除されました。

ここでは数ナノ秒の節約がないため、これは残念なことです。CPU時間は、人間が手作業で最適化した累積時間の多くを補う可能性があります。あなたの上司はどちらにもっとお金を払うのですか:あなたの時間の1時間、またはコンピュータが実行されている1時間?どの時点でプラグを抜いて、より高速なコンピュータを購入する時が来たと認めますか?

明らかに、コードだけでなく、優先順位を最適化する必要があります。私の最後の答えでは、2つのコードスニペットの違いを利用しました。

try/ を使用するcatch

int x;
try {
    x = int.Parse("1234");
}
catch {
    return;
}
// some more code here...

try/ を使用しないcatch

int x;
if (int.TryParse("1234", out x) == false) {
    return;
}
// some more code here

プロファイリング/最適化(上記でカバー)でない場合は、時間を浪費する可能性が高いメンテナンス開発者の観点から検討します。これは、try/のcatch問題がなければ不要である可能性が高いため、スクロールする必要があります。ソースコード...それらの1つには、4行のボイラープレートガベージが追加されています。

クラスに導入されるフィールドが増えるにつれ、このボイラープレートのガベージはすべて(ソースコードと逆アセンブルされたコードの両方で)合理的なレベルを超えて蓄積されます。フィールドごとに4つの余分な行があり、それらは常に同じ行です...繰り返しを避けることを教えられていませんか?自作の抽象化の背後にtry/を隠すことはできると思いますcatchが、それでは例外を回避することもできます(つまり、を使用Int.TryParse)。

これは複雑な例でもありません。try/に新しいクラスをインスタンス化する試みを見てきましたcatch。コンストラクター内のすべてのコードは、コンパイラーによって自動的に適用される特定の最適化から失格になる可能性があることを考慮してください。コンパイラーは指示されたとおりに実行しているのではなく、コンパイラーが遅いという理論を生み出すためのより良い方法は何ですか?

上記のコンストラクターによって例外がスローされ、その結果いくつかのバグがトリガーされると想定すると、貧しいメンテナンス開発者はそれを追跡する必要があります。gotoの悪夢のスパゲッティコードとは異なり、try/ はスタックを同じメソッドの他の部分だけでなく他のクラスやメソッドにも移動catchできるため、3次元で混乱を引き起こす可能性があるため、これはそれほど簡単な作業ではないかもしれません。、すべてがメンテナンス開発者によって監視されますが、難しい方法です!しかし、「後藤は危険だ」と言われていますね。

最後に、try/にcatchはメリットがあります。つまり、最適化を無効にするように設計されています。もしそうなら、それはデバッグ支援です!それはそれがそのために設計されたものであり、それがそれとして使用されるべきものです...

それも良い点だと思います。これは、マルチスレッドアプリケーションの安全で正気なメッセージパッシングアルゴリズムを無効にする可能性のある最適化を無効にし、可能な競合状態をキャッチするために使用できます;)これが、try / catchを使用すると考えられる唯一のシナリオです。それにも選択肢があります。


どのような最適化を行うtrycatchfinally無効?

AKA

はどのようにtrycatchそしてfinallyデバッグの助けとして役立ちますか?

それらは書き込みバリアです。これは標準から来ています:

12.3.3.13 try-catchステートメント

次の形式のステートメントstmtの場合:

try try-block
catch ( ... ) catch-block-1
... 
catch ( ... ) catch-block-n
  • 明確な割り当て状態Vの冒頭のtry-ブロックがの明確な割り当て状態と同じであるVの冒頭のstmt
  • 明確な割り当て状態Vの冒頭にキャッチブロック-I (任意のため、私は)の明確な割り当て状態と同じであるVの冒頭のstmt
  • 明確な割り当て状態Vのエンドポイントでのstmtは(および場合のみ)あれば間違いなく割り当てられているVは間違いのエンドポイントに割り当てられているのtryブロックおよびすべてのキャッチブロック-iのすべてのための(I 1からn個)。

言い換えると、各tryステートメントの始めに:

  • tryステートメントに入る前に可視オブジェクトに行われたすべての割り当ては完了している必要があります。これには、開始時にスレッドロックが必要であり、競合状態のデバッグに役立ちます。
  • コンパイラは次のことを許可されていません:
    • tryステートメントの前に確実に割り当てられていた未使用の変数割り当てを削除する
    • その内部割り当てのいずれかを再編成または合体します(つまり、まだ行っていない場合は、最初のリンクを参照してください)。
    • このバリアを超えるホイスト割り当て、後で使用されることがわかっている変数への割り当てを遅らせる(まったく使用する場合)、または他の最適化を可能にするために後から先に割り当てを先に進める...

同様のストーリーが各catchステートメントに当てはまります。あなたの内と仮定try(のは、言わせ声明(またはコンストラクタまたはそれが呼び出す機能など)を使用すると、そのそれ以外の場合は無意味な変数に割り当てるgarbage=42;)、コンパイラはその文、それはプログラムの観察可能な行動にどのように関係のないどんなにを排除することはできません。割り当ては、ブロックに入る前に完了する必要がありますcatch

それは価値があるものについてfinallyは、同様に低下する物語を伝えます:

12.3.3.14 Try-finallyステートメント

次の形式のtryステートメントstmtの場合:

try try-block
finally finally-block

•の明確な割り当て状態V の冒頭のtry-ブロックがの明確な割り当て状態と同じであるVの冒頭のstmt
•の明確な割り当て状態Vの初めに最終的にブロックがの明確な割り当て状態と同じであるVの冒頭のstmt
•の明確な割り当て状態Vのエンドポイントでのstmtは間違いなく場合(および場合のみ)が割り当てられ、次のいずれかのOのVは間違いのエンドポイントに割り当てられているのtryブロック O V間違いのエンドポイントに割り当てられ、最終的にブロック (のような制御フロー転送した場合のgoto文が)行われ以内に始まることを試み、ブロック、および外の両端のtryブロック、そしてVはまた、間違いなくその上で割り当てられて考えられていますfinally-blockのエンドポイントにvが確実に割り当てられている場合の制御フロー転送。(これは唯一の場合ではありません。vがこの制御フロー転送で別の理由で確実に割り当てられている場合でも、確実に割り当てられていると見なされます。)

12.3.3.15 try-catch-finallyステートメント

以下のための明確な代入解析のtry - キャッチ - 最後に、フォームの声明:

try try-block
catch ( ... ) catch-block-1
... 
catch ( ... ) catch-block-n
finally finally-block

声明であるかのように行われている試み - 最終的に囲む声明のtry - キャッチステートメントを:

try { 
    try   
    try-block
    catch ( ... ) catch-block-1
    ...   
    catch ( ... ) catch-block-n
} 
finally finally-block

3

頻繁に呼び出されるメソッド内にあるかどうかは言うまでもありませんが、アプリケーションの全体的な動作に影響を与える可能性があります。
たとえば、Int32.Parseの使用は、他の方法で簡単にキャッチできる何かに対して例外をスローするため、ほとんどの場合、悪い習慣と見なしています。

したがって、ここに記述されているすべてを完了するには、次のようにします
。1)try..catchブロックを使用して予期しないエラーをキャッチします-パフォーマンスの低下はほとんどありません。
2)回避できる場合は、例外エラーに例外を使用しないでください。


3

当時、これについて質問する人が多かったので、しばらく前に記事を書きました。これとテストコードは、http://www.blackwasp.co.uk/SpeedTestTryCatch.aspxにあります

結論としては、try / catchブロックにはわずかなオーバーヘッドがありますが、無視できるほど小さいためです。ただし、数百万回実行されるループでtry / catchブロックを実行している場合は、可能であればブロックをループの外側に移動することを検討してください。

try / catchブロックの主要なパフォーマンス問題は、実際に例外をキャッチするときです。これは、アプリケーションに顕著な遅延を追加する可能性があります。もちろん、問題が発生した場合、ほとんどの開発者(および多くのユーザー)は、一時停止をこれから発生する例外として認識します。ここで重要なのは、通常の操作で例外処理を使用しないことです。名前が示すように、それらは例外的であり、それらがスローされないようにできる限りのことを行う必要があります。正しく機能しているプログラムの予想されるフローの一部として使用しないでください。


2

私は、作られたブログエントリ昨年のこのテーマについてを。見てみな。結論として、例外が発生しなければ、tryブロックのコストはほとんどかかりません。私のラップトップでは、例外は約36μsでした。それは予想よりも少ないかもしれませんが、これらの結果は浅いスタック上にあることに注意してください。また、最初の例外は本当に遅いです。


(;あなたが使用している接続がタイムアウトしている私はあなたのブログを達することができなかったtry/ catchあわやあわやすぎ?)、しかし、あなたはまた、測定結果を提供する、件名にブログを書かれている言語仕様といくつかのMSのMVPと議論しているようですあなたのアドバイスに反して...私が行った研究は間違っているという提案に私はオープンですが、それが何を言っているかを確認するためにあなたのブログエントリを読む必要があります。
2017

@Hafthorのブログ投稿に加えて、速度のパフォーマンスの違いをテストするために特別に記述されたコードを含む別のブログ投稿があります。結果によると、例外がわずか5%の時間で発生した場合、例外処理コードの実行は、非例外処理コードよりも全体で100倍遅くなります。この記事は特にtry-catchブロックとtryparse()メソッドを対象としていますが、概念は同じです。

2

コンパイラエラーメッセージ、コード分析警告メッセージ、およびルーチンで受け入れられる例外(特に、1つの場所でスローされ、別の場所で受け入れられる例外)のないコードを記述、デバッグ、および保守する方がはるかに簡単です。コードがより簡単になるため、平均してコードの記述が改善され、バグが少なくなります。

私にとって、そのプログラマーと品質のオーバーヘッドは、プロセスフローにtry-catchを使用することに対する第一の議論です。

例外によるコンピューターのオーバーヘッドは、それと比較して取るに足らないものであり、通常、アプリケーションが実際のパフォーマンス要件を満たす能力に関してはごくわずかです。


@リチャードT、なぜ?プログラマーや品質のオーバーヘッドと比較すると、それは重要ではありません。
ThunderGr 2013

1

私はHafthorのブログ投稿が本当に好きです。このディスカッションに2セントを追加するために、データレイヤーで1種類の例外(DataAccessException)のみをスローさせるのは常に簡単だと言いたいのです。このようにして、私のビジネスレイヤーはどのような例外を予期し、それをキャッチします。次に、他のビジネスルールに応じて(つまり、ビジネスオブジェクトがワークフローに参加している場合など)、新しい例外(BusinessObjectException)をスローするか、再スローせずに続行します。

必要なときにいつでもtry..catchを使用して、賢く使用することをためらわないでください!

たとえば、このメソッドはワークフローに参加しています...

コメント?

public bool DeleteGallery(int id)
{
    try
    {
        using (var transaction = new DbTransactionManager())
        {
            try
            {
                transaction.BeginTransaction();

                _galleryRepository.DeleteGallery(id, transaction);
                _galleryRepository.DeletePictures(id, transaction);

                FileManager.DeleteAll(id);

                transaction.Commit();
            }
            catch (DataAccessException ex)
            {
                Logger.Log(ex);
                transaction.Rollback();                        
                throw new BusinessObjectException("Cannot delete gallery. Ensure business rules and try again.", ex);
            }
        }
    }
    catch (DbTransactionException ex)
    {
        Logger.Log(ex);
        throw new BusinessObjectException("Cannot delete gallery.", ex);
    }
    return true;
}

2
David、 'DeleteGallery'への呼び出しをtry / catchブロックでラップしますか?
ロブ

DeleteGalleryはブール関数であるため、そこで例外をスローしても役に立たないようです。これには、DeleteGalleryの呼び出しをtry / catchブロックで囲む必要があります。if(!DeleteGallery(theid)){// handle}の方がわかりやすくなっています。その特定の例では。
ThunderGr 2013

1

今日のコンパイラは一般的なケース、つまり例外が発生しない場合にオーバーヘッドを追加しないことを、Michael L. Scottによるプログラミング言語プラグマティクスで読むことができます。したがって、すべての作業はコンパイル時に行われます。ただし、実行時に例外がスローされると、コンパイラーはバイナリー検索を実行して正しい例外を見つける必要があり、これはユーザーが新しくスローするたびに発生します。

しかし、例外は例外であり、このコストは完全に許容できます。例外なしで例外処理を実行して、代わりにリターンエラーコードを使用しようとすると、おそらくすべてのサブルーチンにifステートメントが必要になり、これにより実際にリアルタイムのオーバーヘッドが発生します。ifステートメントがいくつかのアセンブリー命令に変換され、サブルーチンに入るたびに実行されることがわかっています。

私の英語についてすみません、それがあなたを助けることを願っています。この情報は引用された本に基づいています。詳細については、8.5例外処理を参照してください。


コンパイラーは実行時に画像が表示されません。そこする必要があります CLRは、例外を処理できるように、のtry / catchブロックのオーバーヘッドも。C#は.NET CLR(仮想マシン)で実行されます。例外がない場合、ブロック自体のオーバーヘッドは最小限であるように見えますが、CLRが例外を処理するコストは非常に重要です。
ThunderGr 2013

-4

使用する必要のない場所で使用した場合の、try / catchブロックの最大コストの1つを分析してみましょう。

int x;
try {
    x = int.Parse("1234");
}
catch {
    return;
}
// some more code here...

そして、これがtry / catchのないものです:

int x;
if (int.TryParse("1234", out x) == false) {
    return;
}
// some more code here

意味のない空白を数えずに、これら2つの同等のコードがバイト単位でほぼ正確に同じ長さであることに気づくかもしれません。後者には4バイト少ないインデントが含まれます。それは悪いことですか?

傷害に侮辱を加えるために、学生は入力がintとして解析されることができる間、ループすることに決めます。try / catchを使用しない場合の解決策は次のようになります。

while (int.TryParse(...))
{
    ...
}

しかし、try / catchを使用する場合、これはどのように見えますか?

try {
    for (;;)
    {
        x = int.Parse(...);
        ...
    }
}
catch
{
    ...
}

try / catchブロックはインデントを無駄にする魔法のような方法ですが、失敗した理由もわかりません!明らかな例外エラーで停止するのではなく、深刻な論理的欠陥を越えてコードが実行され続けるときに、デバッグをしている人がどのように感じるか想像してみてください。try / catchブロックは、怠惰な人間のデータ検証/衛生管理です。

小さなコストの1つは、try / catchブロックが実際に特定の最適化を無効にすることです。http://msmvps.com/blogs/peterritchie/archive/2007/06/22/performance-implications-of-try-catch-finally.aspx。それも良い点だと思います。これは、マルチスレッドアプリケーションの安全で正気なメッセージパッシングアルゴリズムを無効にする可能性のある最適化を無効にし、可能な競合状態をキャッチするために使用できます;)これが、try / catchを使用すると考えられる唯一のシナリオです。それにも選択肢があります。


1
TryParseがtry {int x = int.Parse( "xxx");を実行すると確信しています。return true;} catch {return false; 内部的に。インデントは問題ではなく、パフォーマンスとオーバーヘッドのみが問題です。
ThunderGr 2013

@ThunderGrあるいは、私が投稿した新しい回答を読んでください。これには、より多くのリンクが含まれています。そのうちの1つは、回避Int.Parseした場合の大幅なパフォーマンス向上の分析ですInt.TryParse
2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.