Memoization using the
||= operator is a useful and straightforward performance optimisation. However, this isn’t a suitable solution for cases when the expensive operation might result in
Instead of …
…repeating potentially expensive calculations:
class OldTimeySweetShop def any_jars_nearly_empty? @any_jars_nearly_empty ||= glass_jars.any? do |jar| jar.count_each_sweet_by_hand < 10 end end end
defined? method with an early return.
class OldTimeySweetShop def any_jars_nearly_empty? return @any_jars_nearly_empty if defined?(@any_jars_nearly_empty) @any_jars_nearly_empty = glass_jars.any? do |jar| jar.count_each_sweet_by_hand < 10 end end end
Use of the
#defined? method is technically the more “correct” way to perform memoization.
||= (or equals) operator literally means:
a || a = possibly_expensive_computation
A consequence of this is that if
a is “false-y”, meaning set to
false, then the right-hand side of the
|| is executed. This is potentially a big issue because if the expensive computation is legitimately returning
false, it will be run every time the method is used, completely circumventing the improvement we are attempting.
Enumerable#any? above to illustrate that this
defined?-based technique can be useful to improve performance for methods that loop over large datasets and might return a boolean or
Often, a memoized result won’t be
false, and in that case this style is undoubtably more visually noisy to read, and possibly trickier to understand, when you come back to it later on.
You should be careful when using memoization in a high-throughput, threaded Ruby environment. You probably are.
sidekiq is threaded and many Ruby web servers are as well.
When your code is reaching outside of the Ruby interpreter (e.g. reusable database connections, file handling, writing to a data store, manually creating background threads) it is possible to introduce race conditions if your code is called by multiple threads at the same time. However, you’re unlikely to be doing this in the course of a day-to-day application. Still, it is a possible source of bugs, so be aware!
Last updated on February 8th, 2021 by @andycroll
An email newsletter, with one Ruby/Rails technique delivered with a ‘why?’ and a ‘how?’ every two weeks. It’s deliberately brief, focussed & opinionated.