この問題は、特に組み込み開発にとって重要です。例外処理により、生成されたバイナリ出力にフットプリントが追加されます。一方、例外なく、エラーは他の方法で処理する必要があり、追加のコードが必要になり、最終的にはバイナリサイズも増加します。
私はあなたの経験に特に興味があります:
私の質問はガイダンスとしてのみ取ってください。どんな入力でも大歓迎です。
補遺:特定のC++オブジェクト/実行可能ファイルについて、例外処理専用のコンパイラ生成コードおよびデータ構造によって占有されているロードされたメモリフットプリントの割合を示す具体的なメソッド/スクリプト/ツールを持っている人はいますか?
例外が発生すると、例外処理の実装方法に応じて時間のオーバーヘッドが発生します。ただし、逸話的であるため、例外を引き起こすはずのイベントの重大度は、他の方法を使用して処理するのと同じくらいの時間がかかります。このような問題に対処するために、高度にサポートされている言語ベースの方法を使用してみませんか?
GNU C++コンパイラはデフォルトでゼロコストモデルを使用します。つまり、例外が発生しない場合の時間のオーバーヘッドはありません。
例外処理コードとローカルオブジェクトのオフセットに関する情報は、コンパイル時に1回計算できるため、このような情報は、各ARIにではなく、各関数に関連付けられた1つの場所に保持できます。基本的に、各ARIから例外オーバーヘッドを削除するため、それらをスタックにプッシュするための余分な時間を回避できます。このアプローチは、例外処理のゼロコストモデルと呼ばれ、前述の最適化されたストレージはシャドウスタックと呼ばれます。 -ブルース・エッケル、C++ Volume2で考える
サイズの複雑さのオーバーヘッドは簡単に定量化することはできませんが、Eckelは平均5%と15%と述べています。これは、アプリケーションコードのサイズに対する、例外処理コードのサイズによって異なります。プログラムが小さい場合、例外はバイナリの大部分になります。ゼロコストモデルを使用している場合、例外よりも時間のオーバーヘッドを取り除くためにより多くのスペースが必要になるため、時間ではなくスペースを重視する場合は、ゼロコストのコンパイルを使用しないでください。
私の意見ほとんどの組み込みシステムには十分なメモリがあり、システムにC++コンパイラがある場合は、例外を含めるのに十分なスペースがあります。私のプロジェクトで使用しているPC/104コンピュータには、数GBのセカンダリメモリと512 MBのメインメモリがあるため、例外のスペースの問題はありません。ただし、マイクロコントローラはCでプログラムされています。私のヒューリスティックは「主流のC++コンパイラがあればそれ以外の場合は例外を使用し、それ以外の場合はC "を使用します。
物事の測定、パート2。これで2つのプログラムができました。 1つ目はCであり、gcc-O2でコンパイルされます。
#include <stdio.h>
#include <time.h>
#define BIG 1000000
int f( int n ) {
int r = 0, i = 0;
for ( i = 0; i < 1000; i++ ) {
r += i;
if ( n == BIG - 1 ) {
return -1;
}
}
return r;
}
int main() {
clock_t start = clock();
int i = 0, z = 0;
for ( i = 0; i < BIG; i++ ) {
if ( (z = f(i)) == -1 ) {
break;
}
}
double t = (double)(clock() - start) / CLOCKS_PER_SEC;
printf( "%f\n", t );
printf( "%d\n", z );
}
2つ目はC++で、例外処理があり、g ++-O2でコンパイルされています。
#include <stdio.h>
#include <time.h>
#define BIG 1000000
int f( int n ) {
int r = 0, i = 0;
for ( i = 0; i < 1000; i++ ) {
r += i;
if ( n == BIG - 1 ) {
throw -1;
}
}
return r;
}
int main() {
clock_t start = clock();
int i = 0, z = 0;
for ( i = 0; i < BIG; i++ ) {
try {
z += f(i);
}
catch( ... ) {
break;
}
}
double t = (double)(clock() - start) / CLOCKS_PER_SEC;
printf( "%f\n", t );
printf( "%d\n", z );
}
これらは私の最後の投稿に対するすべての批判に答えると思います。
結果:実行時間により、Cバージョンは例外を除いてC++バージョンよりも0.5%エッジになりますが、他の人が話している(ただし実証されていない)10%ではありません
他の人がコードのコンパイルと実行を試みて(数分しかかからないはずです)、私が恐ろしくて明らかな間違いを犯していないことを確認できれば、とてもありがたいです。これは「科学的方法」として知られています!
私は低遅延環境で働いています。 (本番環境の「チェーン」にある私のアプリケーションでは300マイクロ秒未満)私の経験では、例外処理により、実行量に応じて5〜25%の実行時間が追加されます。
私たちは一般的にバイナリの膨張を気にしませんが、膨張が多すぎると狂ったようにぶつかるので、注意する必要があります。
バイナリを適切に保つだけです(セットアップによって異なります)。
私は自分のシステムのかなり広範なプロファイリングを行っています。
その他の厄介な領域:
ロギング
永続化(これは実行しないか、実行する場合は並行して実行します)
考慮すべきことの1つ:組み込み環境で作業している場合は、アプリケーションをできるだけ小さくする必要があります。 Microsoft Cランタイムは、プログラムにかなりのオーバーヘッドを追加します。要件としてCランタイムを削除することで、単純なプログラムを70キロバイトのファイルではなく2KBのexeファイルにすることができました。これで、サイズのすべての最適化がオンになりました。
C++例外処理には、Cランタイムによって提供されるコンパイラサポートが必要です。詳細は謎に包まれており、まったく文書化されていません。 C++の例外を回避することで、Cランタイムライブラリ全体を切り取ることができました。
動的にリンクするだけだと主張するかもしれませんが、私の場合、それは実用的ではありませんでした。
もう1つの懸念は、C++例外が少なくともMSVCで制限されたRTTI(実行時型情報)を必要とすることです。つまり、例外の型名が実行可能ファイルに格納されます。スペースに関しては、それは問題ではありませんが、ファイルにこの情報がないことは、私にとって「よりクリーンに感じる」だけです。
その特定のプラットフォームのハードウェアとツールチェーンのポートに依存すると思います。
数字はありません。ただし、ほとんどの組み込み開発では、(VxWorks/GCCツールチェーンの場合)2つのことを実行している人を見てきました。
ほとんどの場合、例外処理は両方を利用するため、例外処理も破棄される傾向があります。
本当に金属に近づきたい場合は、setjmp
/longjmp
が使用されます。 これはおそらく可能な(または非常に強力な)最善の解決策ではないことに注意してください。しかし、それが_we_が使用するものです。
例外処理の有無にかかわらず、2つのバージョンのベンチマークスイートを使用してデスクトップで簡単なテストを実行し、最も信頼できるデータを取得できます。
組み込み開発についてのもう一つのこと:テンプレートは疫病のように避けられます-それらはあまりにも多くの膨張を引き起こします。コメントでJohannGerellが説明したように、テンプレートとRTTIに沿って例外タグを付けます(これはよく理解されていると思います)。
繰り返しますが、これは私たちがしていることです。すべての反対票でそれは何ですか?
私の意見では、例外処理は組み込み開発で一般的に受け入れられるものではありません。
GCCもMicrosoftも、「オーバーヘッドゼロ」の例外処理はありません。どちらのコンパイラも、実行範囲を追跡する各関数にプロローグステートメントとエピローグステートメントを挿入します。これにより、パフォーマンスとメモリフットプリントが測定可能に増加します。
私の経験では、パフォーマンスの違いは10%程度です。これは、私の仕事の領域(リアルタイムグラフィックス)にとっては膨大な量です。メモリのオーバーヘッドははるかに少ないですが、それでも重要です-手元にある数字を思い出せませんが、GCC/MSVCを使用すると、プログラムを双方向でコンパイルして違いを測定するのは簡単です。
例外処理を「使用する場合のみ」のコストとして話す人もいます。私が観察したことに基づくと、これは真実ではありません。例外処理を有効にすると、コードパスが例外をスローできるかどうかに関係なく、すべてのコードに影響します(これは、コンパイラの動作を考えると完全に理にかなっています)。
また、組み込み開発ではRTTIを使用しませんが、デバッグビルドでRTTIを使用して、ダウンキャストの結果をサニティチェックします。
バイナリサイズへの影響を確認するのは簡単です。コンパイラでRTTIと例外をオフにするだけです。 dynamic_cast <>を使用している場合は、苦情が寄せられます...ただし、通常、環境ではdynamic_cast <>に依存するコードの使用は避けています。
バイナリサイズの観点から、例外処理とRTTIをオフにすることは常にメリットであることがわかっています。例外処理がない場合のさまざまなエラー処理方法を見てきました。最も人気があるのは、コールスタックに障害コードを渡すことのようです。現在のプロジェクトではsetjmp/longjmpを使用していますが、多くの実装でスコープを終了するときにデストラクタを実行しないため、C++プロジェクトではこれに反対することをお勧めします。正直なところ、特に私たちのプロジェクトがC++であることを考えると、これはコードの元のアーキテクトによる不適切な選択だったと思います。
「埋め込み」を定義します。 8ビットプロセッサでは、例外を除いて動作しないことは確かです(8ビットプロセッサのC++では動作しません)。数年前に誰かのデスクトップになったほど強力なPC104タイプのボードを使用している場合は、それを回避できる可能性があります。しかし、私は尋ねなければなりません-なぜ例外があるのですか?通常、組み込みアプリケーションでは、発生する例外のようなものは考えられません-なぜその問題はテストで解決されなかったのですか?
たとえば、これは医療機器にありますか?医療機器のずさんなソフトウェアは人々を殺しました。計画外の出来事が発生することは容認できません。すべての障害モードを考慮する必要があり、Joel Spolskyが言ったように、例外は、どこから呼び出されたかわからないことを除けば、GOTOステートメントに似ています。では、例外を処理するとき、何が失敗し、デバイスはどのような状態にありますか?あなたの例外のために、あなたの放射線治療装置は満杯で立ち往生していて、誰かを生きたまま調理していますか(これはIRLで起こりました)? 10,000行以上のコードで例外が発生したのはいつですか。確かにそれをおそらく100行のコードに減らすことができるかもしれませんが、例外を引き起こすこれらの各行の重要性を知っていますか?
詳細情報がなければ、組み込みシステムでの例外を計画しないでください。それらを追加する場合は、例外を引き起こす可能性のあるすべてのコード行の障害モードを計画する準備をしてください。あなたが医療機器を作っているなら、あなたがそうしなければ人々は死にます。あなたがポータブルDVDプレーヤーを作っているなら、まあ、あなたは悪いポータブルDVDプレーヤーを作った。どっち?