Island Life

< 実数部が0にならない話 | バックドア >

2013/07/27

保守的gcとスタック

clearstack

Conservative GC 使ってる時に、スタックにポインタぽいものが残ると、もう使ってなくてもメモリが解放されないみたいな話があって、コンパイラで関数から出る時にポインタを持ちうるメモリをクリアすると良いかなぁと作った clang plugin 。

GaucheでもVM stackにポインタが残る可能性はあって前から気にはなっていた。 ただ、それが問題となる状況を実際に起こすのはかなり難しい。Gaucheの場合、 スタックがある程度以上深くなるとヒープにごそっと移されて各フレームが 回収対象になるので、「スタック溢れが起きない程度で、かつ大部分の関数呼び出しよりも 深い呼び出しの中で大きなデータを指すポインタを作る」って条件が結構厳しい。

ただ、可能性があるのは確かで、思わぬ時に噛みつかれる恐れがあるのは落ち着かない。

リターンするたびに使ったスタック領域をクリアする、というのはオーバヘッドがあるし、 ほとんどの場合その領域は直ちに再利用されるので、無駄が多い。

(一応、昔「VM stackの有効部分だけをmarkする」というコードを入れてみたことはあった のだけど、特に性能に変化は見られなかったので無効にしてある。)

最近考えているのは、 ほとんど起きない事象なら、「ある程度の時間間隔で未使用スタック領域をクリアする (例えばGCのsweep phase 10回につき1回とか)」でもいいんじゃないか、という アイディアだ。 いずれクリアされるものなら、いつまでも回収されないという事態は防げる。

その実装自体は難しくないのだけれど、Gaucheの場合VMがスレッド毎にあり、 プロセス中の全てのVMを知るインタフェースが無いのがちと問題。 GCが走ったスレッドに関してだけクリアすると、 止まったままのスレッドのVMがゴミを掴んでいるケースに対応できない。

Boehm GCの方で全スレッドのリストは把握してるので、全VMを辿ることも 強引にできなくはないんだが…

Tags: Gauche, GC

Past comment(s)

shinh (2013/07/28 12:25:05):

未使用スタック領域というのはマシンスタックの、今いるところより小さいアドレスの部分のことでしょうか? それだとすると、 Boehm GC がやってる GC_clear_stack とかがそれなのかなぁと思ってたのですが。それとも VM stack とおっしゃってるのでマシンスタック関係ない方の領域ですかね。 Gauche わかって無いので基本的なことだとすいません。

Ruby の方で話を聞いたことがある問題っていうのは、

http://shinh.skr.jp/m/?date=20081203#p02

にリンクがはってあるやつで、 VM loop をやってるような、デカい関数だとマシンスタックの使いかたがかなり sparse な感じになって、初期化みたいな関数である程度でかい領域確保して、その sparse なマシンスタックの一部に大きいデータを指すポインタが残ってて、その初期化終わったあとは、 sparse なスタックの使いかたしてるせいで、別のパス通ると上書きされず、その後である程度マシンスタックを消費してメインぽいループがはじまって、スタックがある程度以上の深さを保つ状態になるので、全然解放されない、みたいな話だったかと思います。

リターンするたびに stack を clear する、ってのがオーバヘッドが大きいのは全くその通りだと思うのですが、 x86-64 とかだとレジスタ多いから最も頻繁に呼ばれる部分は案外スタックは使ってないと期待できるかもしれないし、パフォーマンスは十分に出てるからとりあえずずっと動いていてくれ系サーバとかだと意外と使えたりするんじゃないかな…とか妄想してました。実際のところどんな感じかは評価してないのでダメダメですが。

shiro (2013/07/28 19:50:32):

ああ、ここでの話はVM stackの方です。「未使用スタック領域」ってのはちと言い方がまずいですね。ある時点で、使われてないスタック領域ってことです。

GaucheもVMのloopをぐるぐる回ってるので、マシンスタックについても同様な問題は確かに発生し得るのですがまだあまり考えてません。loopの下にそれほどスタックは積み重ならなかったような気はするけど…

Post a comment

Name: