callcc {|c| c } while true でメモリリークするという話題 (ruby-core:19846) をきっかけに ruby 1.9 の継続まわりのソースを眺めていたら、継続の作成と呼び出しで毎回 VM stack を丸ごとコピー *1 していることに気がつきました。
rb_thread_mark を見る限り、コピーが必要なのは先頭のスタック部分と終端のコントロールフレーム部分だけ保存すればいいみたいなので、直してみました (ruby-dev:37106) 。
継続を 100 万回作って呼び出す例。
$ time ruby19 -rcontinuation -e 'i = 0; callcc {|c| $c = c }; i += 1; $c.call if i < 1000000' real 1m57.022s user 1m56.780s sys 0m0.180s
$ time ./ruby.fast-cont -rcontinuation -e 'i = 0; callcc {|c| $c = c }; i += 1; $c.call if i < 1000000' real 0m0.660s user 0m0.660s sys 0m0.000s
はやっ。
後で 1.8 を調べたら
$ time ruby18 -e 'i = 0; callcc {|c| $c = c }; i += 1; $c.call if i < 1000000' real 0m1.060s user 0m1.050s sys 0m0.010s
くらいだった。ruby 1.9 は 1.8 より 50 倍速いとか、eval が 3 倍くらい遅いとか言いますが、継続の呼び出しは 120 倍遅かったわけですね。まあ「速さなんて飾り」派のぼくにはどうでもいい話なんですけどね。
ちなみに元々のメモリリークは予想通り、古い継続への参照が新しい継続のキャプチャしたマシンスタックにゴミとして残っているせいだったので、しょうがない。
*1:pthread 環境では 512 KB 。