話の始まりはhttps://sourceforge.net/p/jsk-ros-pkg/tickets/158/ですが調べてみると
https://sourceforge.net/p/jsk-ros-pkg/code/HEAD/tree/trunk/euslisp/test/coords.l
のようにmake-cascoordsを繰り返すだけでメモリが増えていきます.
さらに調べると,以下の用のcopy-objectを使わないとメモリは増えて行きません.
copy-objectがなにかまずいことになっていますでしょうか?
Index: jskeus/eus/lisp/l/coordinates.l =================================================================== --- jskeus/eus/lisp/l/coordinates.l (リビジョン 642) +++ jskeus/eus/lisp/l/coordinates.l (作業コピー) @@ -547,8 +547,9 @@ (setf manager self changed t ;safer worldcoords ;prepare a world-coordinates holder - (instance coordinates :init :rot (copy-object rot) - :pos (copy-object pos))) + (instance coordinates :init :rot (matrix (matrix-row rot 0) (matrix-row rot 1) (matrix-row rot 2)) ;(copy-object rot) + :pos (float-vector (elt pos 0) (elt pos 1) (elt pos 2)) ; (copy-object pos) + )) (if par (send par :assoc self at)) self) )
やはりcopy-objectしたオブジェクトがちゃんと回収されていない気がします.euslisp/test/object.lにテストプログラムがありますので,腕に自信がある人は見てくれると助かります.
原因はわかりました。
copy-objectの中で、mark_lockしていて、
コピーの最中にメモリが足りなくなって、gc出来なくて、メモリを拡張する
という流れです。
対処療法的には、copy-objectがをする前にgcすると大丈夫だと思われます。
根本的な対処方法は、
作戦1:ロックしないようにする
gc用のマークを循環参照しないためのフラグのように使っているのが
コピー中にgcしてほしくない理由に見える。
コピー用のフラグを足すか、使っていないextraなどを使ってコピーすると
コピー中にgcが起きても問題ないかもしれない。
作戦2:コピー中にgcが起こるか事前に調べる
オブジェクトのサイズ等からgcが起こりそうか判定する。
単純にオブジェクトのサイズだけからgcするか決まるわけではなく、
スロット変数のコピーなども行われるのですべてのチェックは
結構複雑になると思われる。
なるほど.
一つ思うのは,テストプログラムでメモリがどんどん増加しますね.
以下で見ると内側のiのループ中はメモリがどんどん確保されていくのはよいとして,
jのループに入るとgcdされて,j=0,1,2でもループの開始時にmarkされているメモリの数は
同じではないでしょうか?つまり,j=0でメモリが増やされるのはいいとして,そのときのi
のループがおわれば,無駄につくったオブジェクトは全部gcして解法されるので,
j=1のなかではj=0の最後につくられたメモリの容量で足りるんではないでしょうか?
なぜj!=0のループでもメモリが増えるのか?というのがどうなっているのでしょうか?
この場合は、iのループの中でgcが起っていないわけではないです。
iのループの中でゴミが溜まっていって、gcが起こるタイミングがcopy-object
の中だったらメモリの拡張が行われるので、確保されるメモリがiのループ全体分
よりも少なくなります。
以下のようにすると、jのループの中ではメモリの追加確保は行われないはずです。
Last edit: Yohei Kakiuchi 2013-05-30
copyobjの中ではh.pmarkを使ってmarkしていますが、
eus.hによるとgcが使用しているのはh.markのようです。
GCがpmarkを使っていない場合に、コピー中にGCしてほしくない理由としては、
完全にコピーできていないセルが勝手にGCされてしまうのを防ぎたい、という意図がありそうです。
これはコピー中のセルをどのタイミングでも誰かが必ず参照しているようにコピーできれば良さそうですが、できないでしょうか。
セルを作った瞬間にmarkが走るとGCされてしまうでしょうか。
pmarkだったら、gcとは競合しないですね。
未検証ですが、問題ありそうなところとしては、
スタックを巧みに使ってコピー元を更新しながら、循環参照を防いでいる。
コピー後にスタックから書き戻しているということをしています。
gcでスタックが変にならなければ(多分大丈夫)問題ないように思います。
スレッドセーフとかの問題もありそうですけど、そもそも、copy-objectはロックしなくても
スレッドセーフじゃないような気がする。
とりあえず、ロックを外して動くか確認するのかな。
とりあえず簡単なスレッドのテストコードをeuslisp/test/object.lに追加しましたが,
mutex_lock(&mark_lock);
をコメントアウトするとやはりsegmentation faultしますね.
ところで,いまさらですが
(equal (make-cube 100 100 100) (make-cube 100 100 100))
するとsegmentation faultするんですが,これって常識でしょうか・
(equal (make-cube 100 100 100) (make-cube 100 100 100))
は循環参照のようです。equalは循環参照を考慮していないように見えます。
bodyのmanagerスロットが自身になっているので、ここで循環しているようです。
知りませんでしたが、最近の変更とは関係なさそうなので、
これまでもそうだったのだと思われます。
pmarkとmarkは関係なく、copyobjの中でGCが動いてもよさそうなので、pmarkを使う関数同士だけがロックしあうように、
pmarkを使っていそうなところのロックをmark_lockからp_mark_lockに変更してみました。
ソースにはp_mark_lockを使っていた痕跡がありますが、現在はコメントアウトされているようです。
これでobject.lのテストは通るようになっていますが、デッドロックの危険などがあるかもしれません。
スレッドのテストなどありますでしょうか。
奥が深そうなので,とりあえず,cascoods の:init メソッドはcopy-matrix, copy-seqを使って書き直しました.
https://sourceforge.net/p/euslisp/code/644/