Systematic Error Handling in C++を見ていました — Andrei Alexandrescuは、C++ の例外は非常に遅いと主張しています。
これは C++98 にも当てはまりますか?
Systematic Error Handling in C++を見ていました — Andrei Alexandrescuは、C++ の例外は非常に遅いと主張しています。
これは C++98 にも当てはまりますか?
現在、例外に使用されている主なモデル (Itanium ABI、VC++ 64 ビット) は、ゼロコスト モデルの例外です。
アイデアは、ガードを設定してあらゆる場所で例外の存在を明示的にチェックすることで時間を無駄にする代わりに、例外をスローする可能性のあるポイント (プログラムカウンター) をハンドラーのリストにマップするサイドテーブルをコンパイラーが生成するというものです。例外がスローされると、このリストが参照されて適切なハンドラー (存在する場合) が選択され、スタックがアンワインドされます。
if (error)
典型的な戦略と比較して:
if
例外が発生した場合、約 10 倍/20 倍のコストがかかりますただし、コストを測定するのは簡単ではありません。
dynamic_cast
は各ハンドラーのテスト)。そのため、ほとんどがキャッシュ ミスであり、純粋な CPU コードと比較すると些細なことではありません。
注: 詳細については、TR18015 レポートの 5.4 章の例外処理 (pdf)を参照してください。
したがって、はい、例外は例外的なパスでは遅くなりますが、それ以外の場合if
は一般的に明示的なチェック (戦略) よりも高速です。
注: Andrei Alexandrescu は、この「より速い」ことに疑問を投げかけているようです。私は個人的に物事が両方向に揺れ動くのを見てきました. 一部のプログラムは例外でより高速であり、他のプログラムは分岐でより高速です.
それは問題ですか?
私はそうではないと主張します。プログラムは、パフォーマンスではなく、読みやすさを念頭に置いて作成する必要があります (少なくとも、最初の基準としてではありません)。例外は、呼び出し元がその場で障害を処理できない、または処理したくないと予想される場合に使用され、それをスタックに渡します。おまけ: C++11 では、標準ライブラリを使用してスレッド間で例外をマーシャリングできます。
これは微妙ですが、map::find
スローすべきではないと主張しますが、 null であるため逆参照の試行が失敗した場合は、スローするをmap::find
返すことに問題はありません: 後者の場合、Alexandrescu が導入したクラスの場合と同様に、呼び出し元が選択します明示的なチェックと例外への依存の間。発信者に責任を負わせることなく発信者に力を与えることは、通常、優れた設計の兆候です。checked_ptr
質問が投稿されたとき、私はタクシーを待って医者に行く途中だったので、短いコメントをする時間しかありませんでした。しかし、今コメントし、賛成票を投じ、反対票を投じたので、自分の答えを追加したほうがよいでしょう。マチューの答えがすでにかなり良いとしても。
クレームについて
「私はC++ での体系的なエラー処理を見ていました — Andrei Alexandrescuは、C++ の例外は非常に遅いと主張しています。」
それが文字通りアンドレイの主張であるなら、完全に間違っていないとしても、彼は非常に誤解を招きやすい. 発生/スローされた例外の場合、プログラミング言語に関係なく、言語の他の基本操作と比較して常に低速です。主張されている主張が示すように、C++だけでなく、他の言語よりもC++でそうです。
一般に、ほとんどの言語に関係なく、複雑なデータ構造を処理するルーチンの呼び出しに変換されるため、他よりも桁違いに遅い 2 つの基本的な言語機能は次のとおりです。
例外のスロー、および
動的メモリ割り当て。
幸いなことに、C++ では、多くの場合、タイム クリティカルなコードでは両方を回避できます。
残念ながら、C++ のデフォルトの効率がかなり近くなったとしても、フリー ランチのようなものはありません。:-) 例外のスローと動的メモリ割り当てを回避することで得られる効率は、通常、C++ を単なる「より良い C」として使用して、より低いレベルの抽象化でコーディングすることによって達成されます。そして、抽象度が低いということは、「複雑さ」が増すことを意味します。
複雑さが増すと、メンテナンスに費やす時間が長くなり、コードの再利用によるメリットがほとんどまたはまったくないことを意味します。これは、見積もりや測定が困難であっても、実際の金銭的コストです。つまり、C++ では、必要に応じて、プログラマーの効率を実行効率と引き換えにすることができます。そうするかどうかは、主に工学的および直感的な決定です。実際には、コストではなく利益のみを簡単に見積もって測定することができるからです。
はい、国際的な C++ 標準化委員会は、C++ のパフォーマンスに関するテクニカル レポート、TR18015 を発行しました。
これは主に、ハンドラーの検索が原因でthrow
、代入などに比べて非常に長い時間がかかる可能性があることを意味します。int
TR18015 のセクション 5.4「例外」で説明されているように、2 つの主要な例外処理の実装戦略があります。
各try
-block が動的に例外キャッチを設定するアプローチ。これにより、例外がスローされたときにハンドラーの動的チェーンの検索が実行されます。
スローされた例外のハンドラを決定するために使用される静的ルックアップ テーブルをコンパイラが生成する方法。
最初の非常に柔軟で一般的なアプローチは、32 ビット Windows ではほとんど強制されますが、64 ビット ランドと *nix ランドでは、2 番目のはるかに効率的なアプローチが一般的に使用されます。
また、そのレポートで説明されているように、各アプローチには、例外処理が効率に影響を与える主な領域が 3 つあります。
try
-ブロック、
通常の機能 (最適化の機会)、および
throw
-式。
主に、動的ハンドラー アプローチ (32 ビット Windows) では、例外処理はtry
ほとんど言語に関係なくブロックに影響を与えます (これは Windows の構造化例外処理スキームによって強制されるため) が、静的テーブル アプローチではtry
-ブロック。これについて議論するには、SO の回答として実用的であるよりもはるかに多くのスペースと調査が必要になります。というわけで、詳細はレポートをご覧ください。
残念ながら、2006 年からのレポートは、2012 年後半の時点ですでに少し古くなっています。私の知る限り、これより新しいものはありません。
別の重要な観点は、例外の使用がパフォーマンスに与える影響は、サポートする言語機能の個別の効率とは大きく異なるということです。なぜなら、レポートが指摘しているように、
「例外処理を検討するときは、エラーを処理する別の方法と対比する必要があります。」
例えば:
プログラミング スタイルの違いによるメンテナンス コスト (正確性)
冗長なコール サイトのif
障害チェックと集中型の障害チェックtry
キャッシュの問題 (短いコードがキャッシュに収まるなど)
レポートには考慮すべきさまざまな側面のリストがありますが、いずれにせよ、実行効率に関する確固たる事実を得る唯一の実際的な方法は、おそらく、例外を使用するプログラムと例外を使用しないプログラムを、決められた開発時間の上限内で開発者と一緒に実装することです。それぞれの方法に精通してから、測定します。
ほとんどの場合、正確さは効率よりも優先されます。
例外なく、次のことが簡単に起こります。
一部のコード P は、リソースを取得したり、何らかの情報を計算したりすることを目的としています。
呼び出しコード C は成功/失敗をチェックする必要がありましたが、チェックしていません。
存在しないリソースまたは無効な情報が C に続くコードで使用され、一般的な混乱を引き起こします。
主な問題はポイント (2) です。通常のリターン コードスキームでは、呼び出し元のコード C は強制的にチェックされません。
このようなチェックを強制する主な方法は 2 つあります。
P は、失敗したときに直接例外をスローします。
P は、C がメイン値を使用する前に検査する必要があるオブジェクトを返します (それ以外の場合は、例外または終了)。
2 番目のアプローチは、私の知る限り、Barton と Nackman が著書 * Scientific and Engineering C++: An Introduction with Advanced Techniques and ExamplesFallow
で最初に説明したもので、「可能な」関数の結果を呼び出すクラスを導入しました。と呼ばれる同様のクラスoptional
が、Boost ライブラリによって提供されるようになりました。また、POD 以外の結果の場合に as 値キャリアをOptional
使用して、自分でクラスを簡単に実装できます。std::vector
最初のアプローチでは、呼び出しコード C は例外処理手法を使用するしかありません。ただし、2 番目のアプローチでは、呼び出しコード C 自体がif
、ベース チェックを行うか、一般的な例外処理を行うかを決定できます。したがって、2 番目のアプローチは、プログラマーと実行時間の効率のトレードオフをサポートします。
「これが C++98 でも当てはまるか知りたい」
C++98 は最初の C++ 標準でした。例外については、例外クラスの標準的な階層が導入されました (残念ながら不完全です)。パフォーマンスへの主な影響は、例外仕様(C++11 で削除) の可能性でしたが、メインの Windows C++ コンパイラによって完全に実装されることはありませんでした。 Visual C++: Visual C++ は C++98 例外仕様構文を受け入れますが、単に無視します。例外仕様。
C++03 は、C++98 の技術的な正誤表にすぎません。C++03 で本当に新しいのは、値の初期化だけです。これは例外とは何の関係もありません。
noexcept
C++11 標準では、一般的な例外仕様が削除され、キーワードに置き換えられました。
C++11 標準では、例外の保存と再スローのサポートも追加されました。これは、C 言語のコールバック間で C++ 例外を伝播するのに最適です。このサポートは、現在の例外を格納する方法を効果的に制限します。ただし、私が知る限り、新しいコードでは C 言語コールバックの両側で例外処理をより簡単に使用できる程度を除いて、パフォーマンスに影響はありません。
コンパイラに依存します。
たとえば、GCC は例外処理時のパフォーマンスが非常に低いことで知られていましたが、これはここ数年で大幅に改善されました。
ただし、例外の処理は、名前が示すように、ソフトウェア設計の規則ではなく、例外であるべきであることに注意してください。毎秒非常に多くの例外をスローしてパフォーマンスに影響を与えるアプリケーションがあり、これが通常の操作と見なされる場合は、別の方法を検討する必要があります。
例外は、面倒なエラー処理コードを取り除くことでコードを読みやすくする優れた方法ですが、例外が通常のプログラム フローの一部になるとすぐに、従うのが非常に難しくなります。aは変装しthrow
た aであることに注意してください。goto catch
はい、でもそれは問題ではありません。なんで?
これを読んでください:
https://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx
catch
基本的に、 Alexandrescu が説明したような例外を使用すること ( asを使用するため、50 倍の速度低下else
) は間違っていると言えます。それは、私がC ++ 22 :)のようなものを追加することを望んでいるような人々のために言われています
:
result = attempt<lexical_cast<int>>("12345"); //lexical_cast is boost function, 'attempt'
//... is the language construct that pretty much generates function from lexical_cast, generated function is the same as the original one except that fact that throws are replaced by return(and exception type that was in place of the return is placed in a result, but NO exception is thrown)...
//... By default std::exception is replaced, ofc precise configuration is possible
if (result)
{
int x = result.get(); // or result.result;
}
else
{
// even possible to see what is the exception that would have happened in original function
switch (result.exception_type())
//...
}
PSまた、例外がそれほど遅くても...実行中にコードのその部分に多くの時間を費やさなければ問題ではないことに注意してください...たとえば、浮動小数点数の除算が遅く、4倍にする場合時間の 0.3% を FP 除算に費やしても問題ありません。
インシリコのように、その実装に依存すると述べましたが、一般に、例外はどの実装でも遅いと見なされ、パフォーマンス集約型のコードでは使用しないでください。
編集: まったく使用しないと言っているわけではありませんが、パフォーマンスを重視するコードの場合は避けるのが最善です。