Re:Boehm GC の Repeated allocation of very large block 問題 その3
解決した。結局やった事は以下の通り。
(1) GMP の allocator には GC のものは使用せず、malloc, realloc, free を使用する
(2) Bignum, Ratnum は gc_cleanup を継承し GC 時にデストラクタが呼ばれるようにする
(3) Bignum, Ratnum のデストラクタで mpz_clear, mpq_clear を呼ぶ
(4) realloc に hook して GC_gcollect を呼ぶ
(1) は GMP の mpz_t が生成する "false pointer" を GC の感知しない領域に追い出すため
(2), (3) は不要になったタイミングで mpz_clear などが確実に呼ばれるようにするため
(4) は enami さんの指摘で分かったのだが、GC が malloc/realloc が呼ばれた回数、割り当てたサイズを知らないので、適切な GC タイミングを教えるという意図。
現状では以下のように少なくとも 30 MB 毎に GC されるようになっている。(この値はいくつか実験をして適当に決めた)
static void* gmp_realloc(void *ptr, size_t oldSize, size_t newSize) { static uintptr_t totalSize = 0; totalSize += newSize; if (totalSize > 30 * 1024 * 1024) { GC_gcollect(); totalSize = 0; } return realloc(ptr, newSize); }
またついでに無駄な mpz_t を作らないように、かなりがんばったので、以前よりだいぶ速くなったと思う。
% mosh -v Mosh R6RS scheme interpreter, version 0.1.2 sewashi% mosh mosh>(define (fact n) (let f ((n n) (r 1)) (if (< n 2) r (f (- n 1) (* r n))))) #<unspecified> mosh>(time (and (fact 100000) #t)) ;;9.126263 real 8.396524 user 0.676042 sys #t
というわけで約 9 秒になった。これは劇的に速い。(ほとんど GMP のおかげなんですが)
Ikarus の trunk で試したら 6.5 秒だったので VM としてはかなりがんばっている感じかな。