平行処理ライブラリの書き換えをしていてスレッドと動的環境(今回はパラメータ限定)に関連した挙動の違いが気になったのでメモ。
動作の調査としては、スレッドAで作成されたパラメータが親スレッド、スレッドAが生成される前から存在したスレッドB及びスレッドAの処理が終了後に生成されたスレッドCでどのように見えるかというもの。使用したのは以下のコード:
;; (param) library
(define-library (param)
(export *param*)
(import (scheme base))
(begin
(define *param* (make-parameter 10))))
;; script
(import (scheme base) (scheme load) (scheme write) (scheme eval) (srfi 18))
(define box (make-vector 1))
(define lock (make-mutex))
(define waiter (make-condition-variable))
(define t
(thread-start!
(make-thread
(lambda ()
(mutex-unlock! lock waiter)
((vector-ref box 0))))))
(define (do-param v)
(display (current-thread))
(display (eval '(*param*) (environment '(param))))
(eval `(*param* ,v) (environment '(param)))
(display (eval '(*param*) (environment '(param))))
(newline))
(thread-join! (thread-start! (make-thread
(lambda () (do-param ":changed")))))
;; main thread
(do-param ":changed-main")
;; other thread?
(thread-join! (thread-start! (make-thread
(lambda () (do-param ":changed-other")))))
(vector-set! box 0 (lambda () (do-param ":changed-before")))
(condition-variable-broadcast! waiter)
(thread-join! t)
調査はSRFI-18が使えるChibi、Gauche、Sagittariusでのみ。FomentやChickenも
cond-expand
を使って必要な部分だけ何とかすればいけるんだけど、面倒だったので今回はパス。悲しいことにR6RS処理系はSagittariusを除いてSRFI-18をサポートしてないのでそれらもパス。
結果は以下の通り
Chibi
#<Context -2373536>10:changed
#<Context -2555840>:changed:changed-main
#<Context -2108736>:changed-main:changed-other
#<Context -2412960>:changed-other:changed-before
Gauche
#<thread #f runnable 0x800a2a10>10:changed
#<thread "root" runnable 0x800a2e60>10:changed-main
#<thread #f runnable 0x800a28a0>:changed-main:changed-other
#<thread #f runnable 0x800a2b80>10:changed-before
Sagittarius
#<thread thread-9 runnable 0x80204320>10:changed
#<thread root runnable 0x8014ce10>#f:changed-main
#<thread thread-10 runnable 0x80204190>:changed-main:changed-other
#<thread thread-7 runnable 0x802044b0>#f:changed-before
Chibiは全てのスレッドでパラメータの共有しているっぽく、スレッドセーフではない模様。デフォルトでのビルドなのでビルドオプションによっては何かあるかもしれない。
GaucheはShiroさんから聞いていた通りスレッドをまたいでも初期値が保持される模様。
Sagittariusは別スレッドで作成されたパラメータの初期値は取れない、まぁ分かっていたことだけど。 親スレッドで作成されたものは子スレッドに渡るので必要なら親スレッドで準備すればよい話なのだが、これが面倒になってきたので何とかしたいなぁというのもある。
Shiroさんからの情報によると、Gaucheではパラメータの初期値はグローバルに持っていてごにょごにょやっているっぽい。ただ、局所的に作成されたパラメータはGCされないとのこと。(Weakハッシュテーブルとかでなんとかできないのだろうか?) 一応確認してみた。
(import (scheme base))
(do ((i 0 (+ i 1))) ((= i 100000)) (make-parameter 1))
;;(do ((i 0 (+ i 1))) ((= i 100000)) (cons 1 2))
;; trigger GC
(do ((i 0 (+ i 1))) ((= i 100)) (make-vector 100))
パラメータを作る場合と単なるコンスセルの場合でどれくらいメモリ割り当てが違うか。明示的には書いてないが、
-f collect-stats
オプションを渡すとGCの状況を教えてくれるのでそれを使用。っで以下が結果。
# parameter
% gosh -r7 -f collect-stats test.scm
;; Statistics (*: main thread only):
;; GC: 14524416bytes heap, 1334816808bytes allocated
;; stack overflow*: 0times, 0.00ms total/0.00ms avg
# cons
% gosh -r7 -f collect-stats test.scm
;; Statistics (*: main thread only):
;; GC: 5943296bytes heap, 11413768bytes allocated
;; stack overflow*: 0times, 0.00ms total/0.00ms avg
コンスセルとパラメータのサイズの違いは調べてないので分からないけど、GCが起きたと仮定するとパラメータとコンスセルの場合で同じメモリになるはずなので、パラメータは回収されていないようにみえる。
動作的にはGaucheの動作が望ましいので、何か手を加えようかな。ただメモリが爆発するのは嫌なので(これが嫌でシンボルもGC対象にしたわけだし)、どうにもならなさそうなら現状維持の方向で。