基盤ソフトウェア 5/29

研究室で寝てたら寝過ごした。

メソッド表を作るのに
多重継承は結構ぐちゃぐちゃなことをやっている
それに、多重継承ができるとダイヤモンドインヘリタンスができてしまう
2つの親クラスにそれぞれ
getRID, setRID
getEID, getEID
が存在し、それぞれ親クラスのメンバidのアクセサとなっている
これでは動きがまずい
2つのサブクラスでidが共有されてしまう
C++の場合はそれをshareするかしないかプログラマが明示的に書く
virtualとか付けて区別できるはず
非常に能力の高いエンジニアであれば良いけれど
ぼーっとしてたら間違えてしまう
途中で設計が変わったりしたらわけがわからなくなってしまう
一部のフィールドだけを共有するのは不可能

そんなわけで多重継承はよくないという風潮が高まった

多重継承には代替案がある
Delegation
has-aとis-a関係を区別する
委譲を使えば多重継承がなくても大体同じようなことはできる

Javaをデザインした人たちは
批判の多い多重継承を使うか
ちょっと面倒だけれどわかりやすいDelegationを使うか選ぶ羽目になって
結局彼らは多重継承を選ばなかった

ところがポリモーフィズムで問題がでてきたので
代替案としてInterface
フィールドのない親クラスならいくら継承しても良い
フィールドがないからフィールドを共有するかしないかという点で問題にならない

しかしまだ一つだけ問題が残っている
メソッド表の問題
初期のJavaVMでは型のない言語と同じ方法を使っていた
つまり表をいちいち探索していてとても遅かった
気の利いたエンジニアはできるだけインターフェースを使わないでプログラマを書く
これは昔の話
今は改善された

例えば巨大なメソッド表を作るアプローチ 空きがたくさん出来てしまう
しかし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について語ると最近のプログラム言語の技術がだいたい判る