22 Jun 2008, 21:30
Photo_9_pragsmall

David Dai (2 posts)

I just want to point out couple potential problems with the memoization code:

1) Keeping the memory in a closure could potentially lead to memory leaks if you don’t have a way to clear it. Over time it will end up with a huge hash that you have no control of sitting in the memory. Another thing to think about is the input domain of the memorized method. If the input domain is extremely large, you’re probably better off using memcache or drb so memoization bit is not coupled to the process that’s hosting the class.

2) Be careful when memoizing methods with hashes or arrays as your parameters. For instance if you change the expensive_discount_calculation() to the following, your cache will fail because Ruby will generate a different hash key for each of the args arrays:


# ...
   def expensive_discount_calculation(*skus)
    skus.pop if skus.last.is_a? Hash
    puts "Expensive calculation for #{skus.inspect}"
    skus.inject {|m,n| m + n }
   end
# ...
d = Discounter.new
puts d.discount(1,2,3, :foo => 'bar')  
puts d.discount(1,2,3, :foo => 'bar')  #=>  no cache here.
23 Jun 2008, 04:00
Dave_gnome_head_isolated_pragsmall

Dave Thomas (337 posts)

David:

wrt point 1, you’re right—this is a “feature” of all basic caches. In these examples, I really wanted to demonstrate the metaprogramming—caching is just a convenient excuse.

wrt point 2. There’s definitely an issue when passing hashes. Arrays can be more forgiving.

Thanks for the comments

Dave

12 Jun 2012, 14:40
Generic-user-small

Cassiano D'Andrea (2 posts)

Dave,

One problem I see with the memoization technique shown is the sharing of values among distinct instances of the same class, what might lead to incorrect results. So for example, running this:

d = Discounter.new
puts d.discount(1,2,3)
puts d.discount(1,2,3)
puts d.discount(2,3,4)
puts d.discount(2,3,4)

e = Discounter.new
puts e.discount(1,2,3)
puts e.discount(1,2,3)
puts e.discount(2,3,4)
puts e.discount(2,3,4)

Will produce the following:

Expensive calculation for [1, 2, 3]
6
6
Expensive calculation for [2, 3, 4]
9
9
6
6
9
9

One possible solution to this would be to include the caller into the hash, when remembering the values. Check here: https://gist.github.com/2917713

Does it make sense to you?

Regards,

Cassiano.

12 Jun 2012, 14:53
Dave_gnome_head_isolated_pragsmall

Dave Thomas (337 posts)

I think the output of your example is what I’d expect. Memoization is appropriate when calling a particular method with a given set of parameters always produces the same results. If the result also depends on the context, then that context also need to be included in the memoization lookup, or you need a per instance lookup.

13 Jun 2012, 12:20
Generic-user-small

Cassiano D'Andrea (2 posts)

Dave, thanks for the quick reply. I get your point. Anyway, I think this decision could be left to the owner of the class, who would decide whether it would make sense to include the context into the memoization process or not, on a method basis. So the user could do something like:

...
remember :discount, :context => true

Look at https://gist.github.com/2923694 for both scenarios.

  You must be logged in to comment