When to use memoization in Ruby on Rails

删除回忆录丶 提交于 2019-11-28 03:24:15
Theo

I think many Rails developers don't fully understand what memoization does and how it works. I've seen it applied to methods that return lazy loaded collections (like a Sequel dataset), or applied to methods that take no arguments but calculate something based on instance variables. In the first case the memoization is nothing but overhead, and in the second it's a source of nasty and hard to track down bugs.

I would not apply memoization if

  • the returned value is merely slightly expensive to calculate. It would have to be very expensive, and not further optimizable, for it to be worth memoization.
  • the returned value is or could be lazy loaded
  • the method is not a pure function, i.e. it is guaranteed to return exactly the same value for the same arguments -- and only uses the arguments to do it's work, or other pure functions. Using instance variables or calling methods that in turn uses instance variables means that the method could return different results for the same arguments.

There are other situations too where memoization isn't appropriate, such as the one in the question and the answers above, but these are three that I think aren't as obvious.

The last item is probably the most important: memoization caches a result based on the arguments to the method, if the method looks like this it cannot be memoized:

def unmemoizable1(name)
  "%s was here %s" % name, Time.now.strftime('%Y-%m-%d')
end

def unmemoizable2
  find_by_shoe_size(@size)
end

Both can, however, be rewritten to take advantage of memoization (although in these two cases it should obviously not be done for other reasons):

def unmemoizable1(name)
  memoizable1(name, Time.now.strftime('%Y-%m-%d'))
end

def memoizable1(name, time)
  "#{name} was here #{time}"
end
memoize :memoizable1

def unmemoizable2
  memoizable2(@size)
end

def memoizable2(size)
  find_by_shoe_size(size)
end
memoize :memoizable2

(assuming that find_by_shoe_size didn't have, or relied on, any side effects)

The trick is to extract a pure function from the method and apply memoization to that instead.

When a method fetches data from multiple tables, and perform some calculations before returning the resulting object, and this method is multiple times in requests, memoization might make sense.

Remember that query caching is also active, so only memoize methods which perform in-Ruby calculations, not pure database fetches.

Perhaps my experience is a good example of when NOT to use memoize. In my Order model, I was memoizing both simple calculation results, i.e. Order#subtotal, Order#tax; as well as model objects, i.e. Order#most_recent_credit_card_used. In the latter, when memoizing a method that returns a CreditCard object, I would get 'frozen hash' errors when attempting to update attributes on the memoized object. Order#most_recent_credit_card_used.frozen? returned true when the method was memoized, which of course, is not what I intended.

My take-away was simple: use memoize for expensive operations that return simple data types (integers, floats, etc.) but Do Not use memoize when returning complex objects like ActiveRecord models, esp. if you intend to update those objects in-memory.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!