Memoize |
BasicWerk
EC Support
Technique
Facebook
|
20140824155339_ruby_Hash_memoize |
|
ruby_Hash_memoize
この関数は毎回新しいオブジェクトを作って返す(でも文字列としての値は同じ)。
def oid
@o = "aaa"
puts "#{@o} #{@o.object_id}"
end
> oid
aaa 70116235649760
=> nil
> oid
aaa 70116235752500
=> nil
一度作ったオブジェクトを繰り返し参照するようにするには ||= で2回目以降の代入を避ける。
def oid
@o ||= "aaa"
puts "#{@o} #{@o.object_id}"
end
> oid
aaa 70116235752500
=> nil
> oid
aaa 70116235752500
=> nil
このことを踏まえて、Hash.new を使ったメモ化の手法を見てみよう。
まず Hash.new は「未定義の key を要求された時にデフォルトで返す value を予約できる」というものだ。 この「予約できる」のは単なる値に限らない。 つまりコードブロックも予約できるのだ。
> h = Hash.new {|hash, key| hash[key] = "#{key} #{key.object_id}"}
=> {}
# 存在しない key が呼び出された時にだけコードブロックが実行される
> h["aaa"]
=> "aaa 70116240527500"
# だから(というか Hash とはそういうものだが)同じ key で再度呼び出しても
# その object_id は変わらない
> h["aaa"]
=> "aaa 70116240527500"
メモ化の例としてこんな状況を考えよう。 テキストが何万個とある。でも、それを編集する正規表現は限定されている。 つまり正規表現での置換の部分をメモ化する。
# 比較のためにメモ化なしバージョンも定義する
def uncached_replace(str)
str.gsub(/(\w)\w+/, "\\1")
end
def cached_replace(str)
# この関数が初めて呼ばれた時に Hash が定義される
# key(この場合 str) に対する処理が予約されると言い換えてもいい
@rep ||= Hash.new do |h, k|
h[k] = k.gsub(/(\w)\w+/, "\\1")
end
# 未知の str に対しては予約された処理を実行して Hash に登録した上で結果を返す
# 既知の str に対しては単に Hash にメモ化済みの値を返す
@rep[str]
end
ベンチマークを測って比べてみよう。
require "benchmark"
# test sample
# この文字列を、
text = <<EOT
aaa aaa aaa
bbb bbb bbb
ccc ccc ccc
EOT
# => "aaa aaa aaa\nbbb bbb bbb\nccc ccc ccc\n"
# 50万回繰り返し処理させる
N = 500000
Benchmark.bmbm do |bm|
bm.report("uncached") do
N.times{ uncached_replace(text) }
end
bm.report("cached") do
N.times{ cached_replace(text) }
end
end
# 実行結果
user system total real
uncached 6.320000 0.150000 6.470000 ( 6.456125)
cached 0.120000 0.000000 0.120000 ( 0.126552)
頻繁に同じ処理の呼び出しがある場合はメモ化が有効だ。
なお Memoizable というモジュールは、上記のようなメモ化を自動で行ってくれる。 コードが複雑になるのを避けたいときはこのようなモジュールを使うのがベターだ。 ただし、モジュール側のオブジェクト生成によるオーバーヘッドがあるので、自作したメモ化より速度は劣る。
http://rubydoc.info/gems/memoizable/0.4.2/frames
おまけ。 文字列オブジェクトの生成を重複させないようにするだけでも速度は改善される。
def uncached_but_string_once_replace(str)
@rgx ||= /(\w)\w+/
@rep ||= "\\1"
str.gsub(@rgx, @rep)
end
Benchmark.bmbm do |bm|
bm.report("uncached") do
N.times{ uncached_replace(text) }
end
bm.report("once") do
N.times{ uncached_but_string_once_replace(text) }
end
end
user system total real
uncached 6.280000 0.100000 6.380000 ( 6.383904)
once 4.390000 0.030000 4.420000 ( 4.410662)
|
| © Shin Nakamura/BasicWerk 2014 |