Memoization: Faster Ruby app
Share on twitter
Share on facebook
Share on linkedin
Share on tumblr

Memoization: Faster Ruby app

The performance impact that your code could have in a certain programming language and/or platform is often overlooked, which might make you prone to blame those tools, instead of analyzing what and how your implementation made your application slower. Memoization may help you to improve your application speed with a non-significant impact on readability or flexibility on your code when appropriately used.

What is Memoization?

Memoization is a technique where you can cache a process result in order to use it later without the need to run that process again. This has the potential to save a massive amount of computing/networking power, thus improving the general performance of your application.

How does it work in Ruby?

You can memoize a value in Ruby using instance variables because they live long after a method has finished.

@value # => nil
def memoize_value
  @value = 'value'
  value = 'value'
end
memoize_value
@value # => 'value'
value # => NameError (undefined local variable or method `value' for main:Object)

And here comes the magic of Ruby: the ||=  (or equals) operator. Since instance variables won’t rise an exception when accessed before being declared, you can use the “or equals” operator:

@value # => nil
@value ||= 'value'
@value # => 'value'
@value ||= 'value2'
@value # => 'value'

Here we are saying to Ruby:
“If you don’t have a truthy value for @value variable, then assign it the value at the right”
With this knowledge you can extrapolate it to an instance method:

class User
  def value
    @value ||= 'value'
  end
end
user = User.new
# This will assign the 'value' since @value is nil
user.value # => 'value'
# This will use the cached value in @value
user.value # => 'value'

As long as user instance reference exists, that value will still be on memory.
This is a simple example, but what would happen if instead of a string value, that assignment made a database query? Or an API call? A heavy object creation?
Here’s an example of a dummy user model with a dummy database connection object:

class User
  def update(properties)
    database.update(properties)
  end
  def destroy
    database.destroy(id)
  end
  private
  def database
    @database ||= Database.new
  end
end
user = User.find(1) # 1 being the user id
# This will assign the database object since @database is nil
user.update(name: 'John')
# This will use the cached Database object in @database
user.destroy

How to update cached values

There’s not a “clean” way of updating a cached value, the only way is to directly override the instance variable like so:

def update_value
  @value = 'new value'
end

Keep in mind that memoized values’ ideal use case is for never-changing values.

Formatting and styling

When you need to memoize a certain not-so-straightforward value, you tend to write code like this:

def value
  return @value if @value
  step_value = some_method
  @value = another_method(step_value).property
end

Here you need to pay special attention to what each line of code does: 
“Did you finish the routine early because a certain value is not present?”
“Is this just an assignment method?”
You can certainly rewrite the code so it removes that unwanted cognitive load from you:

def value
  @value ||= calculate_value
end
def calculate_value
  step_value = some_method
  another_method(step_value).property
end

This will work; it does what it says it does, but now you have created a rather verbose method to encapsulate an obvious task.
Finally, you can rewrite it, as this is taking advantage of a ruby block:

def value
  @value ||= begin
    step_value = some_method
    another_method(step_value).property
  end
end

This way, the code clearly expressed its intention, and you didn’t need a special method for it, you just used a block that does what the previous calculate_value method did. Now, within the same method, you read: 
“Oh, this is a memoized value”
“…And here is the source of that value”

Conclusion

As you may see, Memoization can improve your application’s performance, and the greatest thing about it is that it comes with a little to no-downside on your code readability.
Remember, to use it properly, you need to find the kind of calculated values and/or reused objects which are being processed expecting the same result over and over again in your application, then memoize them and enjoy your milliseconds of saved time!
 
 

Share on twitter
Share on facebook
Share on linkedin
Share on tumblr