evalとシンボル

Ruby用ゲームライブラリのMiyakoの2.1.9がリリースされて、なんだか2.1系も熟成されてきたなあーという感じ。
ところでMyakoの公式サポート掲示板がしばらく前にできている。
http://cyross.bbs.fc2.com/
テトリス作ってる人がいて、なんだか楽しそう。
我がDXRubyはというと、ちょっと前に偶然見つけちゃったDXRuby製のゲームが一つと、いま作ってる人が公開してくれた体験版(前の3Dダンジョンゲーとは別)を二つほど確認済み。
ここからリンクはっていいのかも何も確認してないからとりあえず無しで。なんとなく慎重になってしまうライブラリ作者の図。
それからSourceForgeで公開されてるRDGC(http://sourceforge.jp/projects/rdgc/)、かな。
使ってくれてる人もいることだし俺もがんばらなあかんなーと思いつつ、まあ、マイペースで。


今回は自分のことを書こうと思ったわけではなくて、Miyakoの掲示板での話題より。
何かって言うと、evalが遅いという話。
リンク先のソースを眺めてみると、シンボルを生成するのに:と文字列を繋いでからevalしていたので、とりあえず「String#to_symを使うとevalしなくて済む」、とアドバイスしておこうかと思ったのだが、ことが実行速度の話だけに、それで本当に速くなるのだろうかと疑問に。
試してみた。

require 'benchmark'

col = 'red'
puts Benchmark.measure {
  for i in 0..1000000
    ('dark_' + col).to_sym
  end
}
puts Benchmark.measure {
  for i in 0..1000000
    eval(':dark_' + col)
  end
}

Ruby1.8.7で。

  1.063000   0.000000   1.063000 (  1.109375)
  3.656000   0.000000   3.656000 (  3.671875)

次はRuby1.9.1(mingw32版)で。

  0.625000   0.000000   0.625000 (  0.625000)
  6.907000   0.125000   7.032000 (  7.140625)

サイロス氏の言うとおり、evalを使うと1.9.1のほうがずっと遅い。文字列を繋いでto_symすると、1.9.1の場合では10倍以上の高速化となるようだ。
こういうのは作り方もいろいろあって、インターフェイスを変えずに、つまり'red'という文字列から:dark_redというシンボルを手に入れたければ、ハッシュに突っ込んでしまえばもっと速くなる

require 'benchmark'

hash_dark = {'red'=>:dark_red}
col = 'red'
puts Benchmark.measure {
  for i in 0..1000000
    hash_dark[col]
  end
}
  0.203000   0.000000   0.203000 (  0.203125)

し、ハッシュのキーを文字列にすると内部処理が増えるから、インターフェイスレベルで変えてもいいならこいつもシンボルにすれば更に速くもなる。

require 'benchmark'

hash_dark = {:red=>:dark_red}
col = :red
puts Benchmark.measure {
  for i in 0..1000000
    hash_dark[col]
  end
}
  0.125000   0.000000   0.125000 (  0.125000)

ハッシュのアクセスの時間が惜しければ配列にするという手も有効だ。

require 'benchmark'

RED = 0
array_dark = [:dark_red]
col = RED
puts Benchmark.measure {
  for i in 0..1000000
    array_dark[col]
  end
}
  0.110000   0.000000   0.110000 (  0.109375)

うむ、evalを使うのと比べて60倍以上速くなったじゃないか。
to_symしなさい、というアドバイスではなく、そもそも別の仕組みを考えたほうが効果がありそうだ。