memoize expensive methods in ruby 2.x

With the advent of Ruby 2.x and the prepend module feature memoization has become very easy. According to it’s wiki:

In computing, memoization is an optimization technique used primarily to speed up computer programs by having function calls avoid repeating the calculation of results for previously processed inputs. Memoization has also been used in other contexts (and for purposes other than speed gains), such as in simple mutually recursive descent parsing[1] in a general top-down parsing algorithm[2][3] that accommodates ambiguity and left recursion in polynomial time and space. Although related to caching, memoization refers to a specific case of this optimization, distinguishing it from forms of caching such as buffering or page replacement. In the context of some logic programming languages, memoization is also known as tabling

Lets see how can we achieve memoization in Ruby 2.x using Module#prepend feature. First lets see, how method dispatching works. In ruby method lookup works from bottom to top and every object in Ruby has ancestor chain as well. For e.g.
class Aend

A.ancestors
# => [A, Object, Kernel, BasicObject]

If you call a method on the object of A class then first it would look for it’s defination in A class , then in Object class and so on. In case you include a module in a class then lets see what happens

 

module M
end

class A
  include M
end

A.ancestors
# => [A, M, Object, Kernel, BasicObject]

Module M gets injectedafter” A class in the ancestor chain. Now lets see what Module#prepend does.

 

module M
end

class A
 prepend M
end
A.ancestors
# => [M, A, Object, Kernel, BasicObject]

Prepend injects the module M “before” the A class in the ancestor chain. Now, how can we utilize this to memoize our expensive functions or methods. One such example of expensive method call is making an external API call when IO is blocking the execution further and hence increases the computation time.

Our module where we would implement the memoization technique:

 

module M

 def memoize(method_name)

  memoizer = Module.new do
   define_method method_name do
    @memoized_methods ||= {}
 
    if @memoized_methods.keys.include?(method_name)
     @memoized_methods[method_name]
    else
     @memoized_methods[method_name] = super()
    end
   end
  end
 
  prepend memoizer
 end
end

 

Our class which has expensive methods defined in it.

 

class A
 extend M
 
 def my_expensive_method
  sleep 2
  result = "This method is memoized now"
  return result
 end
 
 memoize :my_expensive_method
end
a = A.new 
a.my_expensive_method

This will take around 2 seconds to run and then result will be me memoized. When you call an expensive method again it will return the result immediately.

We can have the memoize functionality in Ruby 1.9 as well, but that includes creating aliases of the methods, renaming them and then performing caculations. Ruby 2.x gives you a cleaner way to implement this feature.
In case you have more implementations of Module#prepend feature then please don’t forget to mention that in the comments section or do write an email to me “naveenagarwal287@gmail.com” :).

Advertisements

12 thoughts on “memoize expensive methods in ruby 2.x

    • Frankly telling you, It’s been more than 3 years now I have not used windows machine. If you wish you can switch to the unix machine to say least you can switch to Ubuntu. It is advisable and for your future developments needs to move right now. Once you move to Ubuntu its pretty easy to setup the rails development environment and you will be spared with all such problems which just comes with windows.

  1. I checked with libxml but its not getting installed in my windows box currently we are using ruby 1.9.2 and rails 3.1.10 any other options please…dont mind as i was 2+ year experience guy only need more from you like experienced people

  2. http://miniprofiler.com/ it does support sql server. It is not always true that your database queries makes you app slow, in-fact most of the applciations I have debugged for the perormace, were slow in rendering views. Do compare the time it takes to render your views and time it takes to query the database.

    • Thanks again will check with that link given by you in between use xml.builder for sending data to client(i.e Flex)..

      • Ok, then xml builder might be a problem in your case. By default ruby uses a Ruby-native XML library called REXML. REXML is very slow. If you are using this library then I would recommend switching to LibXML. It is pretty fast in comparison to the native library.
        To install LibXML

        gem install libxml-ruby

        and then in your environment configuration file add this line.

        ActiveSupport::XmlMini.backend = ‘LibXML’

        Try benchmarking after and before including LibXML. You should see the remarkable difference in performance.

  3. Hi can’t we speed up ruby on rails application as we using FLEX as front end and Serverside we are using Ruby on Rails and Database MSSQL 2008 any idea to speed up the request asked by FLEX….

    • There are many ways to speed up the rails application but for that you need to first analize what’s making your application slow and optimize that. One pretty and nice way is to use mini profilier gem and monitor each request. It tells you what part in your request takes how much time for e.g. database, views, middleware etc…

      • Thanks but mini profilier does not support mssql right?
        we are using MSSQL has a database any other when client sending a request it taking time to respond.

  4. This is the best way of using Module@prepend I’ve seen so far. You’re awesome! I’ll start using this way.

What do you think?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s