ruby-prof says: don't pass a block to link_to_function

Posted by Lance Ivy Wed, 17 Oct 2007 11:18:00 GMT

In a recent job, I was asked to investigate the performance of a slow page in an application. Non-rigorous tests (run server in production mode, press F5 in Firefox a few times) showed that the page was averaging 2.4 seconds to load. I did some digging with ruby-prof and discovered a major culprit: Rails’ link_to_function helper is dog slow when you pass it a block. It was (only?) being called 50 times in the page, but when I refactored it to build the JavaScript as a string and pass it as a regular argument I shaved 1.1 seconds off the page load time. Granted, it’s still a slow page, but that was a sweet payoff for refactoring two lines of code.

In Detail

The block syntax is elegant, yes, but to support it Rails creates this JavaScriptGenerator object and extends it with every module in ActionView::Base that’s not in the ActionView::Helpers namespace. So where’s the slow part?

<div class="codetitle">JavaScriptGenerator</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class JavaScriptGenerator #:nodoc:
  def initialize(context, &block) #:nodoc:
    @context, @lines = context, []
    include_helpers_from_context
    @context.instance_exec(self, &block)
  end

  private
    def include_helpers_from_context
      @context.extended_by.each do |mod|
        extend mod unless mod.name =~ /^ActionView::Helpers/
      end
      extend GeneratorMethods
    end

  # more code ...
end

The problem is on line 11. No, it’s not the extend call, and it’s not the regular expression matching. It’s mod.name. Those 50 instances of link_to_function were causing mod.name to be called 2000 times. I don’t know why the JavaScriptGenerator is done this way instead of using a Delegator pattern, but that’s for someone else to decide.

The Moral

Unless you really need the page generator methods, or you’re not calling link_to_function more than 5 times, for the love of CPU cycles just pass the JavaScript in a string.