研究室で寝てたら寝過ごした。
メソッド表を作るのに
多重継承は結構ぐちゃぐちゃなことをやっている
それに、多重継承ができるとダイヤモンドインヘリタンスができてしまう
2つの親クラスにそれぞれ
getRID, setRID
getEID, getEID
が存在し、それぞれ親クラスのメンバidのアクセサとなっている
これでは動きがまずい
2つのサブクラスでidが共有されてしまう
C++の場合はそれをshareするかしないかプログラマが明示的に書く
virtualとか付けて区別できるはず
非常に能力の高いエンジニアであれば良いけれど
ぼーっとしてたら間違えてしまう
途中で設計が変わったりしたらわけがわからなくなってしまう
一部のフィールドだけを共有するのは不可能
そんなわけで多重継承はよくないという風潮が高まった
多重継承には代替案がある
Delegation
has-aとis-a関係を区別する
委譲を使えば多重継承がなくても大体同じようなことはできる
Javaをデザインした人たちは
批判の多い多重継承を使うか
ちょっと面倒だけれどわかりやすいDelegationを使うか選ぶ羽目になって
結局彼らは多重継承を選ばなかった
ところがポリモーフィズムで問題がでてきたので
代替案としてInterface
フィールドのない親クラスならいくら継承しても良い
フィールドがないからフィールドを共有するかしないかという点で問題にならない
しかしまだ一つだけ問題が残っている
メソッド表の問題
初期のJavaのVMでは型のない言語と同じ方法を使っていた
つまり表をいちいち探索していてとても遅かった
気の利いたエンジニアはできるだけインターフェースを使わないでプログラマを書く
これは昔の話
今は改善された
例えば巨大なメソッド表を作るアプローチ 空きがたくさん出来てしまう
しかしJavaのクラスは動的にロードされるので
コンパイル時にはどういうクラスがあるのか事前にわからない
じゃあどうしているのか
しかしそのやり方は公表していない
こういう風にやれば早くなるという論文はIBMから一応出ている
もしかしたら相変わらず遅いのかもしれない
ハッシュ関数を使ったメソッド表
論文で実験している範囲内では早い
全体で1つ、全インターフェースで1つの表を持つ
ハッシュ値が被ったら中継メソッドを用意する
他のテクニックもごちゃごちゃあるけど
C言語と比べてもそんなに遅くはない
課題
CとJavaでほぼ同等なプログラムを書いて速度を比較する
レポートは出せば単位はくるけど
できるだけCとJavaの得意不得意がよくわかる例題を選んで欲しい
Garbage collection
メモリ管理
Cとか
C++では手動
コンパイラは性能が悪い
人間は性能が良い
Freeし忘れ memory leak
その逆はdangling pointer 実は使用中
使用中なのに間違って開放してしまう
ポインタが間違って同じところを指している
全部freeするのをやめるというのも
デバッグのテクニックの一つ
気にせずメモリをバカスカ使うというのも手
昔の
Lispには
GCのないものというものがあって
朝から立ち上げて5時ごろになってメモリがいっぱいになる
そしたら1日の仕事は終わりなので帰る
また聞きなので本当かどうかわからないけれど…
夕方までメモリが持てばよいという考え
でもサーヴァーみたいなプログラムの場合はそういう牧歌的なことも言ってられない
C/C++プログラマの誇り
「正しくfreeを支持できるから私のプログラムは早い」
という誇りを持っているかどうか質問をすると昔は1人か2人は手を挙げたものだけれど
今年は誰も手を挙げない
最近の人は謙虚なんですかねえ
昔は
GCは遅いという偏見があった
ついこの間まで(2000年くらい)こういう主張をする人たちは世の中にいっぱい居た
C用の
GCを使っていると「お前はへなちょこだ」と言われたものだ
具体的にどうやって使われていないメモリを探し出すか
Reference counting
メモリブロックに参照カウンタを含める
確保したメモリの最初の4バイト余分に確保してカウンタに使う
例えば q = p; を実行するとカウンタが1増える
q = null; を実行するとカウンタが1減る
0になったらどこからも指されていないということがわかるので
そうなったら回収してもよい
欠点
メモリブロックに循環があると永遠に回収されない
どうしても回収したければループを探し出したりしないといけない
Reference countingは
C++ではライブラリによって実装可能
Smart pointerによる実装
C++の人がどうしても
GCを使いたい時はこのようなライブラリを使う
Mark and Sweep
Root setを決める
これは
局所変数や大域変数などの回収しなくていいもの
そこから到達可能なオブジェクトに印を付けて(Mark)、
印の付かなかったものを回収(Sweep)
ではReference countingとMark and Sweepのどちらが早いか
昔の
LispはMart and Sweepが良く使われていた
当時の性能だと、Martが始まるとプログラムがばたっと止まって数秒待たされる
この挙動が
GCの評判を悪くしていた
Reference countingと比べて、トータルな時間を比べるとどちらか早いかよくわからないが
インタラクティブなアプリケーションの場合は、
止まっている時間が長いのはユーザはすごく気になる
昔はReference countingの方が好まれていた
しかし今はMarkの時間も短くなっているので
インタラクション性が悪いということはない
Eclipseは動かし始めは遅いがべつに気にならない
(動かし始めが遅いのは
JITの問題
良く使うコードは
機械語に置き換わっていくのでだんだん早くなっていく)
一般論ではReference countingは悪い
アルゴリズム
GCによるストップタイプが短いのが利点だが
ポインタの付け替えの度にコストが発生
Reference countingを使った方がいいのは、
極端に
インタラクティブなレスポンスを短くしたほうがいい場合
Mark and Sweepはまずまずの性能
Markは生きているオブジェクトの個数に比例
Sweepはヒープ領域の大きさに比例
S = "a" + "b";S = S + "c";
Javaの場合、文字列を使うとどんどん新しくオブジェクトを作ってどんどん使われる
小さいオブジェクトをどんどん作ってどんどん捨てる
このような状況では早い
逆に言うと、オブジェクトをなかなか捨てないプログラムの場合はあまり効率がよくない
Memory fragmentationの可能性がある
未使用領域がヒープ領域内に散らばる
Copying
GC
ヒープをsemi-spaceに等分し、片方だけを使用
生き残るオブジェクトだけ毎回コピー
# この辺あまり聞いてない
Generational
GC
新世代と旧世代にヒープを分割
作られた後しばらく生き延びているオブジェクトは最後まで生き残っている可能性が高い
一般的にはこの2つを組み合わせている
Javaについて語ると最近のプログラム言語の技術がだいたい判る