ハッシュのキーにシンボル、は速い?
RHGを少し読んでみた。第1部の第2章を半分ぐらい。分からないことだらけだけど、どうも鼻血が出そうにおもしろそうだということが分かった。文章も謎解き風で、非常におもしろいし、まじめなのか不真面目なのか分からないようなノリが楽しい。
ところでQnilの「Q」は何だろう。RならわかるがなぜQなのか。聞いてみたところ答えは「Emacsがそうだったから」だそうだ。意外に楽しくなかった。
とか思わず画面の前で吹いてしまった。ruby.hには、以下のように真偽値の定義がある。
#define Qfalse 0 /* Rubyのfalse */ #define Qtrue 2 /* Rubyのtrue */ #define Qnil 4 /* Rubyのnil */
ぼくはこれをパッと見たとき、何となくQはQuadのQじゃないかと思った。ともあれ、これであちこちのメソッド定義関数にあるreturn Qtrueとかいうのが何を返しているのかが具体的に分かった。
で、ソースのあちこちに書いてあるVALUEはオブジェクトのポインタ(にキャストされることになる32bitのunsigined long)ということが分かった。そして、シンボルというのは、小さな整数とかと同様に、このVALUEに直接埋め込まれているものらしい。小さな数値を扱うだけでオブジェクトは大げさでコストが高いってことか。うーん、良く分からないけど、VALUEとは別にIDというやっぱり32bitの値があって、それは文字列と1対1に対応している。で、それが表出したものがシンボルという。
ハッシュのキーにシンボルを使うといいよという話だったけど、それってつまりハッシュ値を計算していないってことなんだろうか。じゃあ、そもそもハッシュじゃないような気もするけど、使う側にとってハッシュの本質はキーの文字列が一意に何かをポイントしてくれるだから、それでいいってことなのかな。
とりあえず実行速度を計ってみた。マシンはCore 2 Duoの1.86GHzとか。
s1.rb
h = {:apple => 1, :banana => 2, :citrus => 3} 1000000.times { h[:apple] += 1 h[:banana] += 1 h[:citrus] += 1 } puts "apple: #{h[:apple]}" puts "banana: #{h[:banana]}" puts "citrus: #{h[:citrus]}"
s2.rb
h = {"apple" => 1, "banana" => 2, "citrus" => 3} 1000000.times { h["apple"] += 1 h["banana"] += 1 h["citrus"] += 1 } puts "apple: #{h["apple"]}" puts "banana: #{h["banana"]}" puts "citrus: #{h["citrus"]}"
$ time ruby s1.rb apple: 1000001 banana: 1000002 citrus: 1000003 real 0m2.560s user 0m2.404s sys 0m0.124s $ time ruby s2.rb apple: 1000001 banana: 1000002 citrus: 1000003 real 0m4.532s user 0m4.320s sys 0m0.168s $
なるほど、確かにシンボルを使ったほうがだいぶ速い……、けど、まあよほどでないと、どうでもいい違いだ。ハッシュのキーにシンボルを使うのは、えーと、意図を明確にする意味があるとか?
Ruby1.9でもやってみた。
$ time ~/src/ruby/ruby s1.rb apple: 1000001 banana: 1000002 citrus: 1000003 real 0m1.023s user 0m0.992s sys 0m0.012s $ time ~/src/ruby/ruby s2.rb apple: 1000001 banana: 1000002 citrus: 1000003 real 0m3.040s user 0m3.000s sys 0m0.020s $
うーむ、1.8より1.9のほうが差が大きい。ハッシュの計算ってCで書かれてて、1.8でも1.9でも、あんまり差がつかない比較的重たい処理だから、とか?