KH-FDPLのノート0002
(1) 一般的なオブジェクトの寿命の管理モデル
- C++ではnewでオブジェクトを作ってdeleteで破棄していました。また自動変数(スタック上にとられる変数)はスコープから抜けるときに自動でデストラクタが呼び出されていました。
- この自動変数の挙動は便利で分かりやすくて深刻なメモリリークバグも起こりにくいと思いますが、自分でnewして作ったオブジェクトについてはdeleteを呼び忘れたりしてメモリリークバグの大きな原因となっています。
- JavaではGC(ガーベージコレクション)を採用し、プログラマがdeleteしないでもいいようにしました。
- これは一見すると非常にかっこいいのですが、実際は処理系が未使用のオブジェクトをうまく検出できずにメモリリークしてしまったり(特に循環参照などが起こるとリークしやすい)、full-GCが始まるとプログラムがしばらくの間とまってしまう問題があります。
- GCは多くの場合ではプログラマを寿命管理から解放してくれます。しかしそのせいでこれに配慮しないことに慣れてしまい、リークやfull-GCに悩まされ始めた時にどうしていいかわからなくて途方に暮れることになります。これはあまり教育的な仕様とは言えないでしょう。
- これらに対する比較的新しい方法として、autorelease方式があります。iOSのObjective-Cで使われています。
- これはとても良い方法だと思いました。KH-FDPLでも最初はこの方法をそのまま取り入れようと思っていたくらいです。
- ただautoreleaseしない場合に、retainやreleaseで参照カウンタを管理しなければいけません。その部分の難易度が従来と大差ないと思いました。ここで間違えればやはりメモリリークしてしまいます。
- 最近のarcの仕組みを使うと、retainやreleaseを意識しなくても自動でかなりうまくやってくれるそうです。もはや循環参照以外でリークすることはほとんどないそうです。
(2) KH-FDPLのオブジェクトの寿命の管理モデル
- KH-FDPLは参照カウンタを持ちません。結局こういうものをプログラマに管理させていると、永久にメモリリーク問題はなくならないと思うからです。
- 短期間で作って壊すようなオブジェクトは、まさにautoreleaseと同じ方法で管理します。次回の回収時に回収されるプールがあるので、そこに登録するわけです。
- そしてそれより長く生き残るオブジェクトに関しては、どのプールに所属するかを「new時に」指定します。どんなプログラムも、最低一つのプールを持っていますし、関数呼び出しをすればそのたびにプールが作られます。それらの中のどのプールに所属するのかをnew時に指定すればいいのです。
- これにより、そのプールが回収されるときに必ずオブジェクトは回収されます。リークしません。
- もし寿命を変更したくなれば、あとから所属するプールを変更すればいいです。それで問題ありません。
- プールを指定するなんて言うとややこしく聞こえますが、ファイルを作るときに所属パスを指定するのと同じくらいの感覚です。何も指定しなければ、次回回収プールがデフォルトで選ばれます(autorelease相当)。関数の戻り値用のオブジェクトは、次々回回収プールがデフォルトになります。
- そもそもオブジェクトを作るときが、もっともオブジェクトの寿命について考えているときでもあるのです。オブジェクトを作るときは、そのオブジェクトを何のために作るのかわかっていますし(目的)、だからいついらなくなるのかもわかっているのです。
- KH-FDPLはオブジェクトが基本的に永続性なので、メモリリークは非常に深刻です。
だからこんなにリークさせない仕組みにこだわっているのです。
- KH-FDPLでリークを起こしてしまったら、それは再起動後にも残るので、放置していると永久に残ります。他の言語環境ならリークしても再起動すれば済みますが、KH-FDPLではそうは行かないわけです。まあ自分でせっせとリークしたオブジェクトを特定して消していけばいいといえばそれまでですが、それよりはリークを起こさない仕組みを考えるほうが建設的だと思います。
(3) じゃあ他の管理アルゴリズムは使えないのか?
- KH-FDPLは多様な言語を受け入れたいと思っています。GCが好きな人はGCのあるプログラミング言語を書きたいでしょうし、new-deleteモデルが好きな人はそういうプログラミング言語を書きたいでしょう。・・・OKです。
- まずは簡単なnew-deleteモデルから行きましょう。このモデルの言語では、オブジェクトをすべて一つのプールに所属させておいて、そのプールはアプリが終了するまで残るようにしておきます。これで勝手に消えることは一切なくなります。それで、delete命令が来たら、そのプールから個別に削除してやればいいだけです。そういう動作をするバイトコードをコンパイラが生成すればいいだけです。
- GCの場合も同様です。GCスレッドは、プールの中のすべてのオブジェクトをスキャンすることができます。それで誰からも参照されていないオブジェクトを見つけたら、それを個別に削除すればいいだけです。簡単ですね!
(4) KH-FDPLの方法で本当にうまくいくのか? [ここはかなり具体的なのでより詳しく知りたい人向け]
- new-deleteモデルは、「使わなくなったらdeleteしてください」というだけなので、管理さえできればうまくいくのは自明です。またGCモデルも「誰からも参照されなくなったら勝手に消えます」というだけなので、やはりうまくいくのは自明です。しかし、KH-FDPLは、本当にそれでうまくいくの?と不安になるかもしれません。ということで、詳しい解説を試みます。
- KH-FDPLでは、関数を呼ぶ直前に「スタックフレーム」という「データストア型オブジェクト」を作ります。とりあえずイメージとしてはフォルダみたいなものを考えてください。これがプールになります。
- 関数はローカル変数を使います。そうするとそれらのローカル変数はそのスタックフレームの中に作られていきます。こうすることで他の関数の変数と名前が衝突する心配がありません。
- 関数から抜けると、スタックフレームは削除されます(もしかしたらすぐには消さずに、次の関数呼び出しの直前に消されるかもしれない)。つまりローカル変数は消えることになります。
- 関数が関数を呼び出した場合、スタックフレームの中にスタックフレームが作られて、そこが使われます。こういう仕組みなので再帰とかでも大丈夫です。
こめんと欄
|