Tr18015
- 2. 今日話すこと
TR18015 Chap.5
Language Features:
Overheads and Strategies
• 5.1 Namespaces
• 5.2 Type Conversion Operators
• 5.3 Classes and Inheritance
• 5.4 Exception Handling
• 5.5 Templates
• 5.6 Programmer Directed Optimizations
- 4. 5.1 名前空間
Namespaces
実行の際の空間・時間的ペナルティは無い
名前解決は複雑になる
• ADLとかADLとかADLとか
• Namespaceを使わなければ名前解決も気にす
る必要はない
シンボル名が長くなる分、オブジェクトファイ
ルのシンボルテーブルは大きくなる
- 5. 5.2 型変換演算子
Type Conversion Operators
基本的にCスタイルのキャストと全く同様
可能な限り意図の表現力とgreppability*に
優れたC++スタイルのキャストを使うべき
dynamic_cast (Cスタイルではできないキャスト)のみ
RTTIを使うための追加(時間的・空間的)コス
トが発生する場合がある
* greppability: 検索(grep)のしやすさ。
Cスタイルのキャストはgrepによる検索が不可能
- 6. 5.3 クラスと継承
Classes and Inheritance
「オブジェクト指向」スタイルは、
継承による抽象化を多用する
• どの程度遅くなるのか?あるいはならないのか?
表現方法による実行速度の違いを検証する
• 具体的な環境については不明
• 特殊なコンパイラを使っているわけではない
- 7. 5.3.1 表現のオーバーヘッド
Representation Overheads
C++の「クラス」は2種類
1. 仮想関数を持たないクラス
• Cにおける「struct」と全く同じ
2. 多態クラス(仮想関数を持つクラス)
• オブジェクトごとに一つのポインタ
• クラスごとに一つの仮想関数テーブル
• 仮想関数一つにつきポインタ一つまたは二つ
• クラスごとに一つのRTTIオブジェクト
クラスの主用途は「抽象化」
- 8. 5.3.1 表現のオーバーヘッド
Representation Overheads
抽象化によって、実行時間のオーバーヘッドが
生じる場合がある
• 組込型を小さなクラスに入れる
• スマートポインタを使う
• 仮想関数を使う
このような現象は「抽象化コスト(the abstraction
penalty)」と呼ばれている
抽象化の種類に依るが、最適化の優れた
コンパイラ(現存する少なくとも二つの実装)では
追加コストは3%以下に抑えられている
- 9. 5.3.2 基本的なクラスの操作
Basic Class Operations
クラスの操作にかかるコストを、5種類の
コンパイラ・実行環境の組み合わせで
測定・比較する
• #1 --- コンパイラA・実行環境A
• #2 --- コンパイラB・実行環境A
• #3 --- コンパイラC・実行環境A
• #4 --- コンパイラD・実行環境B
• #5 --- コンパイラE・実行環境B
- 10. 5.3.2 基本的なクラスの操作
Basic Class Operations
メンバ関数とフリー関数の呼び出しコストの違い
• ポインタ経由 -- px->f(1) / g(ps, 1)
• 参照経由 -- x.g(1) / g(&s, 1)
• staticメンバ -- X::h(1) / h(1)
- 11. 5.3.2 基本的なクラスの操作
Basic Class Operations
#1, #3は感覚に合致した結果
• X::h(1)とh(1)は引数が一つ少ない
#2, #5は(inline指定していないにも関わらず)
インライン化されて呼び出しが消えている
#4は少々奇妙な結果になっている
• ただし、インライン化に比べれば些細な差
- 12. 5.3.3 仮想関数
Virtual Functions
仮想関数呼び出しの仕組みとコストは、
関数ポインタ配列経由の呼び出しと同等
• 仮想関数 -- pf->f(1) / x.f(1)
• ポインタ配列 -- p[1](ps,1) / p[1](&s,1)
- 13. 5.3.3 仮想関数
Virtual Functions
#1,#2,#3,#5は非仮想呼び出しに比べ2割ほど遅い
#2,#5は仮想関数をインライン化している
• ただし、インライン化可能な状況は限られている
#4は仮想呼び出しのほうが速くなってしまっている
• 「virtualは遅い」はすべての状況で成り立つわけではない
- 14. 5.3.3.1 クラステンプレートの仮想関数
Virtual functions of class templates
クラステンプレート内の仮想関数は、テンプレートが
実体化される度に(使われていなくても)生成される
• ライブラリでこれをやってしまうと、100KB単位で
オブジェクトコードサイズが増加する
テンプレートにしなくても済む部分は非テンプレート
の外部クラスに追い出す
• 状態変数や生ポインタのハンドリングも
• ios_baseがiosから独立している理由がコレ
仮想関数が大量にあるクラステンプレートは
設計を見直したほうがよい
- 15. 5.3.4 インライン化
Inlining
「インライン化」はどの程度効果があるのか?
• inline無指定 -- px->g(1) / x.g(1)
• inline指定 -- ps->k(1) / x.k(1)
• 関数形式マクロ -- K(ps, 1) / K(&s, 1)
- 16. 5.3.4 インライン化
Inlining
小さい関数では効果は絶大
賢いコンパイラは勝手にinline化している
「inlineキーワードは無視される」は、
賢くないコンパイラには当てはまらない
- 17. 5.3.5 多重継承
Multiple Inheritance
処理系によって内部実装が違う
例えば、メンバ関数へ渡す
thisポインタの調整方法
• 呼び出し側で調整してから渡すか、
• サンクを作り間接呼び出しにするか
struct Devied: Base1, Base2 { };
の時、Base1を左親、Base2を右親とする
- 18. 5.3.5 多重継承
Multiple Inheritance
単一継承・多重継承(左の親/右の親)、
非仮想・仮想呼び出しで比較
• 単一継承 -- px->g(1) / px->f(1)
• 多重(左親) -- pc->g(i) / pa->f(i)
• 多重(右親) -- pc->gg(i) / pb->ff(i)
- 19. 5.3.5 多重継承
Multiple Inheritance
#1, #4は多重継承すると呼び出しがinline化された
#1, #2は単一・多重で呼び出しコストに差が無い
#3, #5は多重継承時に右親の仮想関数を呼ぶと追
加でコストがかかっている
- 20. 5.3.6 仮想基底クラス
Virtual Base Classes
基底クラスへのアクセスが仮想化される
非仮想の単一継承と比較
• 単一継承 -- px->g(1) / px->f(1)
• 仮想継承 -- pd->gg(i) / pa->f(i)
- 21. 5.3.6 仮想基底クラス
Virtual Base Classes
非仮想呼出で…
• #3は少しオーバーヘッドがあるように見える
• #2, #5は明らかに遅くなっている
• #1, #4は仮想継承のほうがなぜか速い
- 22. 5.3.6 仮想基底クラス
Virtual Base Classes
仮想呼出で…
• 非仮想呼出の時よりは影響が小さく見える
• #5のみ明らかにオーバーヘッドがある
• コンパイラによる最適化の効きが影響大
- 23. 5.3.7 型情報
Type Information
いわゆるRTTI
• フリー関数(参考) -- h(1)
• 基底クラス -- typeid(pa)
• 派生クラス -- typeid(pc)
• 仮想基底クラス -- typeid(pa)
• 仮想継承 -- typeid(pd)
- 24. 5.3.7 型情報
Type Information
仮想継承かどうかはパフォーマンスには
影響していない
基底か派生かは影響を受ける
どちらにせよ、関数呼び出しよりも明らかに遅い
- 25. 5.3.8 動的キャスト
Dynamic Cast
Vtblの参照、type_infoの比較、thisの調整を
一度に行うキャスト
• Down-cast -- 基底クラスへのキャスト
• Up-cast -- 派生クラスへのキャスト
• Cross-cast -- 横方向へのキャスト
「オブジェクト指向言語」のキャストは大体コレ
- 26. 5.3.8 動的キャスト
Dynamic Cast
• 仮想関数呼び出し -- px->f(1)
• 左親へのup-cast -- cast(pa, pc)
• 右親へのup-cast -- cast(pb, pc)
• 左親からdown-cast -- cast(pc, pa)
• 右親からdown-cast -- cast(pc, pb)
- 27. 5.3.8 動的キャスト
Dynamic Cast
多段継承もあるが同じ結果なので省略
Up-castはstatic_castと変わらない
Down-castは仮想関数呼び出しより遅い
Cross-castは壊滅的に遅い
• 本来はUp-cast + Down-castで実現できるはずだが…
- 28. 5.4 例外処理
Exception Handling
詳しく説明すると3時間くらいは語れるので、
Binary Hacks読んでください。
- 29. 5.5 テンプレート
Templates
Templateの無いC++なんてC++じゃない!!!
• が、無条件で便利に使えるわけではない
• 実行時間とコンパイル時間のトレードオフ
- 30. 5.5.1 テンプレートのオーバーヘッド
Templates Overheads
書き方によっては予想外にオブジェクトコード
が膨張することがある
T*に対する操作をvoid*に追い出すのは
常套手段 (thin-wrapper)
• 例: list<T*>の実装を裏でplist<T>に委譲
→plist<T>は内部でvoid*にキャストする
• 賢いコンパイラでは自動でマージするらしい
- 31. 5.5.2 テンプレート vs. 継承
Templates vs. Inheritance
自明でないプログラムには、通常コンテナと
アルゴリズムが必要
• C++はTemplateによる実装で用意(STL)
• .NETのGenericコンテナの中身は継承ベース
• 他の多くは継承ベースの実装で用意
• Smalltalk
• Java
• 他にもたくさん
- 32. 5.5.2 テンプレート vs. 継承
Templates vs. Inheritance
昔々、C++にテンプレートやRTTIが
導入される前の話
• NIH Class Libraryというライブラリがあった
• 当然継承ベースでの実装
• ほとんどのメンバ関数がvirtual
• RTTIも自前で用意
• Object型から派生したクラスを操作するよう設計
- 33. 5.5.2 テンプレート vs. 継承
Templates vs. Inheritance
今のtemplateベースの実装に比べると…
• 継承が深すぎて、テンプレートを使わないのに
オブジェクトコードの肥大化を起こしていた
• Object型の派生クラスしか扱えないため、
int等の組込型は直接扱うことはできなかった
• 型安全にできなかった
• 他のライブラリに混ぜて使うことができなかった
後年、C++にテンプレートベースのクラスライ
ブラリ(STL)が導入される
- 34. 5.6 プログラマによる最適化
Programmer Directed Optimizations
本節に対する驚くべき解説を思いついたが、
それを解説するには時間が足りなさすぎる!!!