例外とパフォーマンス

C ++例外のサポートがコードの全体的なパフォーマンスにどのように影響するかについて、ちょっとした調査を投稿することにしました。



私の仕事の経験には、コードを書くときにパフォーマンスが常に考慮されるさまざまな組み込みシステムの開発の数年が含まれます(大量の情報を処理するリアルタイムシステム-プロセッサとメモリの速度はこれまでにありませんでした)。 したがって、この環境では、プログラマーは通常、C ++言語が提供する特定の機能によってオーバーヘッドが発生する(または発生しない)ことをかなりよく理解しています。 例えば 名前空間のサポート-追加費用は一切かかりません。 RTTI-クラス/構造の名前とそのtype_infoを含む追加セクション(結果のバイナリのサイズは増加しますが、これはコード生成には影響しません); など 例外サポートの実装方法( すべてのプラットフォームではありません)を確認します。



使用されるツール:C ++コードをCコードに変換するEDGの古代のフロントエンド、および受信したCファイルをフォーマットするためのアーティスティックスタイル (そうでない場合、それらを読み取ることはできません)。 EDGのフロントエンドを強調する必要があります-これは、Intelコンパイラ、Texas Instrumentsコンパイラなどに組み込まれているフロントエンドです。 C ++のすべての機能をサポートすることは(C言語のすべての機能を実装するのに比べて)非常に難しいタスクであるため、一部のプラットフォームではC ++コードは同一のCコードに変換され、このコードは既にCコンパイラに供給されています。 フロントエンドは最新ではなく、理解に適しています。



したがって、かなり単純なテキストコードを使用してみましょう(名前と定数は、処理済みリストで簡単に見つけられるように特別に選択されています)。

struct AAAAA { int a; virtual void process(); AAAAA() { a = 1234; } virtual ~AAAAA() {} }; struct BBBBB : AAAAA { virtual void process(); BBBBB() { a = 5678; } virtual ~BBBBB() {} }; // forward declaration int bar(); int foo() { BBBBB b1; b1.a = bar(); b1.process(); BBBBB b2; b2.a = bar(); b2.process(); return b1.a + b2.a; }
      
      







すべてが非常に簡単です-2つのインラインコンストラクター/デストラクタ、2つの仮想関数、2つの外部関数呼び出し。



例外サポートなしの結果コードを次に示します(結果はAStyleによって処理されました)。 シートですが、必要です:

 #line 1 "1.cpp" struct __T9639768; struct AAAAA; #line 9 struct BBBBB; struct __T9639768 { short d; short i; void (*f)(); }; #line 1 struct AAAAA { int a; struct __T9639768 *__vptr; }; #line 9 struct BBBBB { struct AAAAA __b_AAAAA; }; #line 17 extern int bar__Fv(void); extern int foo__Fv(void); #line 10 extern void process__5BBBBBFv(struct BBBBB *const); extern struct __T9639768 __vtbl__5AAAAA[3]; extern struct __T9639768 __vtbl__5BBBBB[3]; #line 19 int foo__Fv(void) { auto int __T9722792; auto struct BBBBB b1; auto struct BBBBB b2; #line 21 { { ((b1.__b_AAAAA).__vptr) = __vtbl__5AAAAA; ((b1.__b_AAAAA).a) = 1234; } ((b1.__b_AAAAA).__vptr) = __vtbl__5BBBBB; ((b1.__b_AAAAA).a) = 5678; } ((b1.__b_AAAAA).a) = (bar__Fv()); process__5BBBBBFv((&b1)); { { ((b2.__b_AAAAA).__vptr) = __vtbl__5AAAAA; ((b2.__b_AAAAA).a) = 1234; } ((b2.__b_AAAAA).__vptr) = __vtbl__5BBBBB; ((b2.__b_AAAAA).a) = 5678; } ((b2.__b_AAAAA).a) = (bar__Fv()); process__5BBBBBFv((&b2)); { __T9722792 = ((((b1.__b_AAAAA).a)) + (((b2.__b_AAAAA).a))); { ((b2.__b_AAAAA).__vptr) = __vtbl__5BBBBB; { { ((b2.__b_AAAAA).__vptr) = __vtbl__5AAAAA; } } } { ((b1.__b_AAAAA).__vptr) = __vtbl__5BBBBB; { { ((b1.__b_AAAAA).__vptr) = __vtbl__5AAAAA; } } } return __T9722792; } }
      
      







各オブジェクトがどのように「構築される」か、vtable /継承がどのように実装されるか、コンストラクタ/デストラクタは依然としてインラインであり、Cコンパイラにはこのコードを効果的に最適化するためのすべての情報が残っています。 また、結果のCコードは72行、約1.6kBかかります。



現在、同じソースが例外サポート付きでブロードキャストされています。



ここで結果を参照してください: 253行と8.5 kBのC相当 。 ここでは投稿せず、いくつかのポイントのコメントを付けてメイン関数(以前はint foo()



)に限定します。

 int foo__Fv(void) { static struct __T9641460 __T9653776[2] = {{((void (*)())__dt__5BBBBBFv),((unsigned short)0U),((unsigned short)65535U),((unsigned char)0U)},{((void (*)())__dt__5BBBBBFv),((unsigned short)1U),((unsigned short)0U),((unsigned char)0U)}}; auto void *__T9731464[2]; auto int __T9733536; auto struct #line 20 __T9643156 __T9734356; auto struct BBBBB b1; auto struct BBBBB b2; (__T9734356.next) = __curr_eh_stack_entry; __curr_eh_stack_entry = (&__T9734356); (__T9734356.kind) = ((unsigned char)1U); (((__T9734356.variant).function).regions) = ((struct __T9641460 *)__T9653776); (((__T9734356.variant).function).obj_table) = ((void **)__T9731464); ((( #line 25 __T9734356.variant).function).saved_region_number) = __eh_curr_region; __eh_curr_region = ((unsigned short)65535U); #line 21 __ct__5BBBBBFv((&b1)); (((void **)__T9731464)[0U]) = ((void *)(&b1)); __eh_curr_region = ((unsigned short)0U); ((b1.__b_AAAAA).a) = (bar__Fv()); process__5BBBBBFv((&b1)); __ct__5BBBBBFv((&b2)); (((void **)__T9731464)[1U]) = ((void *)(&b2)); __eh_curr_region = ((unsigned short)1U); ((b2.__b_AAAAA).a) = (bar__Fv()); process__5BBBBBFv((&b2)); { __T9733536 = ((((b1.__b_AAAAA).a)) + (((b2.__b_AAAAA).a))); __eh_curr_region = ((unsigned short)0U); __dt__5BBBBBFv((&b2), 2); __eh_curr_region = ((unsigned short)65535U); __dt__5BBBBBFv((&b1), 2); { __eh_curr_region = ((((__T9734356.variant).function).saved_region_number)); __curr_eh_stack_entry = #line 29 ((__T9734356.next)); return __T9733536; } } }
      
      





主な変更:



最初の2つのポイントの問題は、コンストラクター/デストラクタのロジックが非常に複雑になり、フロントエンドがそれらを別々の機能にすることです。 理由は明らかです-それらを(インラインで)埋め込むと、BBBBタイプのオブジェクトが使用される各関数のコードが大幅に増加します。 しかし、この結果、Cコンパイラーオプティマイザーが生成するコードの生産性が大幅に低下します(コードに追加の呼び出しとチェックがあります)。



つまり、例外のサポートを有効にすると、最終的なバイナリファイルのボリュームが増加し、オブジェクトが構築されるすべての関数の速度が低下しました(最も些細なものを除く)。



実際、これが組み込み開発のデフォルトで例外サポートがオフになっている主な理由です。実際に使用されていなくても、料金を支払う必要があります。



PS:もちろん、これはすべて、「例外が悪い!」または「例外の代わりに戻りコードを使用する」という意味ではありません。 すべてのツールがそのタスクに適しています。

PPS:組み込み開発におけるエラー状況の処理のサポート。もちろん、積極的に使用されています。 彼女は通常C ++例外を使用しません。これは別の記事のトピックです。



All Articles