犬猿の仲
コンパイラの時なんかはほとんどListをSetのごとく使って処理してしまっていたし、オーダーに気を付ければ特に速度的な問題が起こることもなかった。が、最近はもっぱらHashtblである。なんでもガーガーhashにつっこんでしまうのが楽ちんだ。Hashtblモジュールにはhash関数という、どんなオブジェクトからも自由にハッシュ値を計算してくれる('a -> int)な便利な関数があるわけで、ここまで来ると何も考える必要はない。
それはさておき、最近どうしても速度が速くなってくれない部分があって、どうしたものかどうしたものかと思い悩んでいた。どうもとことん調べてみると、単にHashtbl.findしているだけなのだ。これはいったい? 他にもたくさんfindしているところはあるのだが、どうしたことかある特定の一カ所だけ遅い。すると、ふとHashtblにlistをつっこむと遅いという噂を思い出す。まさにそこでhashにつっこんでいるのはlistであった。
ひょっとして、これが問題なのか? そこで、速度を調べることに。
let rec make_n_list n v = if n = 0 then [] else v::make_n_list (n-1) v let to_str l = String.concat "/" (List.map string_of_int l) let timer f = let t = Sys.time () in f (); Printf.printf "%f sec\n" (Sys.time() -. t) let main () = let l_hash = Hashtbl.create 100000 in let s_hash = Hashtbl.create 100000 in let lst = Hashtbl.create 100000 in for i = 0 to 10000 do let l = make_n_list 10 i in Hashtbl.add l_hash l i; Hashtbl.add s_hash (to_str l) i; Hashtbl.add lst l i; done; timer (fun () -> Hashtbl.iter (fun l _ -> let _ = Hashtbl.find l_hash l in ()) lst); timer (fun () -> Hashtbl.iter (fun l _ -> let _ = Hashtbl.find s_hash (to_str l) in ()) lst) ;; main ()
ムダに長いな。なんてことはなくて、長さ10のint listをkeyに10000個つっこんで、読み直しているだけだ。そして、もう一方はint listをstring listにしてから、String.concatで1つのstringにして、それをkeyにhashを引いている。どこからどう見ても後者の方が遅い、様な気がする。気が。しかし、衝撃の事実。
% ./camlprog.exe 2.463000 sec 0.100000 sec
前者の方が20倍近く遅い。ひどい。なにこれ。
やはり、Hashtbl.hashをブラックボックスのままほっといたのがまずかったのだろうか。もしかして、listに対しては常に同じ値を返してたりしないだろうかとか思ったが、流石になかった。あー、なんだろなこれは。
とりあえず、Hashtblとlistは相性が悪いので、同時に使うなと。はい。