Memoization is a simple and powerful performance optimisation technique that all Ruby developers should have full command of.
Here I'll go over three memoisation variants, in order of increasing difficulty:
- Naive
||=
-based memozation - Intermediate
defined?
-based memoization - Advanced memoization for methods with parameters
1. ||=
A popular Ruby idiom, pronounced "or-equals", where assignment only happens if the lefthand side is falsey. Use this where you know that any potential assigned values will never be falsey.
def response
# it is a common practice to store the memoized value in
# an instance variable (ivar) of the same name as the method.
@response ||= expensive_operation
end
2. defined?
-based memoization
A less known pattern that is capable of memoizing falsey values. Also useful if expensive_operation
is a multiline statement.
def response
return @response if defined?(@response)
# NB, this is a regular assignment because
# line above already did the cache check.
@response = expensive_operation
end
3. Memoization for methods with parameters
Sometimes it's useful to memoize several values, depending on the arguments given to a method. This approach can only be used if the values the method returns (and that are about to be memoized) are idempotent i.e. remain the same on repeat calls.
It's a good practice to name the ivar in plural of the method name.
Notice that memo_key
must be something unique based on the arguments given. Using Object#hash
is a general approach, but there can be others, for example, args can be of a known type and have a unique identifier returned by .id
...
def response(arg1, arg2)
@responses ||= {}
memo_key = "#{arg1.hash}_#{arg2.hash}"
return @responses[memo_key] if @responses.key?(memo_key)
@responses[memo_key] = begin
puts "Doing expensive operation"
expensive_operation
end
end
> response(:a, :b)
Doing expensive operation
=> something
> response(:a, :b)
=> something
> response(false, false)
Doing expensive operation
=> something_else
> response(false, false)
=> something_else