1.9 の継続を 170 倍速くした

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 。