したがって、try/catch はオーバーヘッドを追加するため、プロセス フローを制御する良い方法ではないことはわかっていますが、このオーバーヘッドはどこから来て、実際の影響は何ですか?
12 に答える
ここで注意すべき点は次の 3 つです。
まず、実際にコードに try-catch ブロックを使用しても、パフォーマンスがほとんどまたはまったく低下しません。これは、アプリケーションでそれらを使用しないようにする場合に考慮すべきではありません。パフォーマンス ヒットは、例外がスローされたときにのみ発生します。
他の人が言及したスタック巻き戻し操作などに加えて例外がスローされた場合、スタック トレースなどの例外クラスのメンバーを設定するために、ランタイム/リフレクション関連の一連の処理が発生することに注意する必要があります。オブジェクトとさまざまなタイプのメンバーなど
これが、例外を再スローする場合の一般的なアドバイスが
throw;
、例外を再度スローするか、新しい例外を構築するのではなく、単純なスタック情報のすべてが再収集されるためである理由の 1 つだと思います。それはすべて保存されます。
私は言語実装の専門家ではありませんが (だから、これは大まかに考えてください)、最大のコストの 1 つは、スタックを巻き戻し、スタック トレース用に保存することだと思います。これは、例外がスローされたときにのみ発生すると思われますが (わかりません)、そうであれば、例外がスローされるたびに適切なサイズの隠れたコストになります...だから、1 つの場所からジャンプするだけではありません。別のコードでは、多くのことが行われています。
EXCEPTIONAL 動作の例外を使用している限り、それは問題ではないと思います (したがって、プログラムを通る典型的な予想されるパスではありません)。
例外がスローされないときに try/catch/finally を使用することのオーバーヘッド、またはプロセス フローを制御するために例外を使用することのオーバーヘッドについてお尋ねですか? 後者は、ダイナマイトの棒を使用して幼児の誕生日のろうそくに火をつけるのに似ており、関連するオーバーヘッドは次の領域に分類されます。
- 通常はキャッシュに存在しない常駐データにアクセスする例外がスローされるため、追加のキャッシュ ミスが発生する可能性があります。
通常はアプリケーションのワーキング セットにない非常駐コードやデータにアクセスする例外がスローされるため、追加のページ フォールトが発生する可能性があります。
- たとえば、例外をスローするには、CLR が、現在の IP と、例外が処理されてフィルター ブロックが処理されるまでのすべてのフレームのリターン IP に基づいて、finally ブロックと catch ブロックの場所を見つける必要があります。
- メタデータの読み取りなど、診断目的でフレームを作成するための追加の構築コストと名前解決。
上記の両方の項目は通常、「コールド」コードとデータにアクセスするため、メモリ プレッシャがまったくない場合、ハード ページ フォールトが発生する可能性があります。
- CLR は、使用頻度の低いコードとデータを、頻繁に使用されるデータから遠く離れた場所に配置しようとします。
- ハード ページ フォールトが発生した場合、そのコストは他のすべてのコストを圧倒します。
- 典型的なキャッチ状況はしばしば深いため、上記の影響が拡大する傾向があります (ページ フォールトの可能性が高くなります)。
コストの実際の影響については、その時点でコード内で他に何が起こっているかによって大きく異なります。Jon Skeet は、いくつかの便利なリンクを含む優れた要約をここに掲載しています。私は、例外がパフォーマンスを著しく損なうところまで来たら、パフォーマンス以外の例外の使用に関して問題があるという彼の声明に同意する傾向があります。
私の経験では、最大のオーバーヘッドは、実際に例外をスローして処理することです。私はかつて、次のようなコードを使用して、誰かがオブジェクトを編集する権利を持っているかどうかを確認するプロジェクトに取り組んでいました。この 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();
}
テスト データベースがテスト データでいっぱいになると、新しいフォームなどを開くときに非常に目に見える速度低下が発生します。
だから私はそれを次のようにリファクタリングしました。これは、後のquick 'n dirty測定によると、約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 桁遅くなります。
頻繁に呼び出されるメソッド内にある場合は言うまでもなく、アプリケーションの全体的な動作に影響を与える可能性があります。
たとえば、Int32.Parseの使用は、他の方法では簡単にキャッチできる例外をスローするため、ほとんどの場合、悪い習慣だと思います。
したがって、ここに記述されているすべてを結論付けるには、次のようにします
。1)try..catchブロックを使用して、予期しないエラーをキャッチします。パフォーマンスの低下はほとんどありません。
2)回避できる場合は、例外エラーの例外を使用しないでください。
当時、これについて多くの人が尋ねていたので、私はしばらく前にこれについての記事を書きました. これとテスト コードはhttp://www.blackwasp.co.uk/SpeedTestTryCatch.aspxにあります。
結果として、try/catch ブロックのオーバーヘッドはわずかですが、非常に小さいため無視する必要があります。ただし、何百万回も実行されるループで try/catch ブロックを実行している場合は、可能であればブロックをループの外に移動することを検討してください。
try/catch ブロックの重要なパフォーマンスの問題は、実際に例外をキャッチするときです。これにより、アプリケーションに顕著な遅延が追加される可能性があります。もちろん、問題が発生した場合、ほとんどの開発者 (および多くのユーザー) は一時停止を、発生しようとしている例外として認識します! ここで重要なのは、通常の操作に例外処理を使用しないことです。名前が示すように、それらは例外的なものであり、スローされないようにできる限りのことを行う必要があります。正しく機能しているプログラムの予想されるフローの一部としてそれらを使用しないでください。
私は昨年、このテーマについてブログエントリを作成しました。見てみな。要するに、例外が発生しない場合、try ブロックのコストはほとんどかからないということです。私のラップトップでは、例外は約 36μs でした。これは予想よりも少ないかもしれませんが、浅いスタックでの結果であることを覚えておいてください。また、最初の例外は非常に遅いです。
コンパイラ エラー メッセージ、コード分析の警告メッセージ、およびルーチンで受け入れられる例外 (特に、ある場所でスローされ、別の場所で受け入れられる例外) のないコードの作成、デバッグ、および保守が非常に簡単になります。より簡単なため、コードは平均してより適切に記述され、バグが少なくなります。
私にとって、そのプログラマーと品質のオーバーヘッドは、プロセス フローに try-catch を使用することに反対する主な理由です。
例外のコンピューター オーバーヘッドは、比較すると取るに足らないものであり、実際のパフォーマンス要件を満たすアプリケーションの能力という点では、通常はごくわずかです。
私は Hafthor のブログ投稿がとても気に入っています。この議論に 2 セント追加するために、私が言いたいのは、DATA LAYER が 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;
}
Michael L. Scott による Programming Languages Pragmatics を読むと、最近のコンパイラは一般的なケース、つまり例外が発生しない場合にオーバーヘッドを追加しないことがわかります。したがって、すべての作業はコンパイル時に行われます。ただし、実行時に例外がスローされると、コンパイラはバイナリ検索を実行して正しい例外を見つける必要があり、これは作成した新しいスローごとに発生します。
しかし、例外は例外であり、このコストは完全に受け入れられます。例外なしで例外処理を実行し、代わりに戻りエラー コードを使用しようとすると、おそらくすべてのサブルーチンに対して if ステートメントが必要になり、これにより実際にリアルタイムのオーバーヘッドが発生します。if ステートメントは、サブルーチンに入るたびに実行されるいくつかのアセンブリ命令に変換されることを知っています。
私の英語で申し訳ありませんが、お役に立てば幸いです。この情報は引用された本に基づいています。詳細については、8.5 章の例外処理を参照してください。
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 ブロックが実際に特定の最適化を無効にすることです。 . それもプラスポイントだと思います。マルチスレッドアプリケーションの安全で健全なメッセージパッシングアルゴリズムを無効にする可能性のある最適化を無効にし、可能な競合状態をキャッチするために使用できます;) try / catchを使用するために私が考えることができる唯一のシナリオについてです。それでも代替手段があります。