{"id":1868,"date":"2011-02-21T12:54:54","date_gmt":"2011-02-21T15:54:54","guid":{"rendered":"http:\/\/blog.plataformatec.com.br\/?p=1868"},"modified":"2011-02-21T17:42:20","modified_gmt":"2011-02-21T20:42:20","slug":"understanding-the-latest-rails-benchmarks","status":"publish","type":"post","link":"https:\/\/blog.plataformatec.com.br\/2011\/02\/understanding-the-latest-rails-benchmarks\/","title":{"rendered":"Understanding the latest Rails benchmarks"},"content":{"rendered":"

Erik DeBill has put two interesting benchmarks on his blog. The first one compares the performance of different Ruby implementations in Rails development mode<\/a> while the second compares their performance in Rails boot time<\/a>. If you haven’t read them yet, please do it now.<\/p>\n

Benchmarking code is an important practice, but it can be misleading if you fail to understand the root causes that lead to the different results.<\/p>\n

Performance in development mode<\/h3>\n

In the first blog post, it is guessed<\/strong> that the root case for having slow requests in development is because Rails eager loads all models and controllers for each request:<\/p>\n

\nNow, what I’d really like is a way to avoid recompiling everything every time. If I could have Rails recompile just the model or controller I’m working on and skip all the others, that’d be grand. I’ve taken a couple stabs at it, but I haven’t succeeded yet.\n<\/p><\/blockquote>\n

This is wrong! Rails, in development, only loads the model and the controller you are using in that specific request. This is very easy to verify if you create a new application, scaffold two resources and add a puts self.name<\/code> in their class definition. If you access one controller, it will only load the model explicitly referenced in that controller. Even the model associations try to be lazy in that aspect, always loading the minimum it can.<\/p>\n

So you may ask, why Rails is getting so slow after adding more scaffolds?<\/p>\n

It happens because Rails 3.0 includes all helpers by default in your ApplicationController<\/code>. So at the beginning of each request, Rails needs to load all helpers. Loading a helper in development mode is slow because ActiveSupport::Dependencies<\/code> needs to track which dependencies were added when a file is loaded. This tracking basically happens by checking which constants were added invoking Object.constants<\/code> before and after the file was loaded. Tracking these constants take more than 50% of the time in the request, mainly because invoking Object.constants<\/code> is slow.<\/p>\n

In other words, the main reason for an implementation to perform better in the benchmarks showed in the blog post is if it can calculate Object.constants<\/code> faster. Those results do not mean at all that an implementation is more suitable than other for Rails development. In order to have real results, we would need a real application that is not made of 1000 scaffold (or, in this case, 1000 helpers).<\/p>\n

In any case, if the root cause is in loading all helpers, how can we make it better? There are a few things:<\/p>\n

1) Obviously, the problem can be fixed by having less helper files. Since Rails scaffold automatically generates helper files, it is common that applications have a bunch of empty helpers. Get rid of them<\/strong>. If you prefer you can even turn off the automatic generation of helpers in scaffold by adding the following to your application configuration:<\/p>\n

\r\nconfig.generators.helper = false\r\n<\/pre>\n

2) If you simply don’t want to include all helpers, there is a method called clear_helpers<\/code> that you could invoke in your ApplicationController<\/code>. This method won’t fix the problem<\/strong> because it is invoked too late, after all the helpers were already loaded. So you get the feature, but not the performance improvement.<\/p>\n

3) Rails master (upcoming Rails 3.1) has a configuration option that allows you to effectively turn these helpers off getting both the feature and the performance improvement:<\/p>\n

\r\nconfig.action_controller.include_all_helpers = false\r\n<\/pre>\n

Boot performance<\/h3>\n

The second blog post shows how Rails boot time performs in different implementations. Since it was not made explicit in which environment those benchmarks were executed, I will assume it happened on development.<\/p>\n

At the end of the second blog post, it tries to associate the performance of booting Rails in development with the amount of code inside the app. However, when you boot an application in development, no model, controller or helper is loaded at all<\/strong> unless you explicitly access them in an initializer or in your routes file. Once again, you can check that by adding some puts<\/code> to your classes.<\/p>\n

So, you may ask one more time, what makes booting up so slow?<\/p>\n

Rails 3 has a new router that can match paths very fast, but in order to do so, it needs to compile each route into a regular expression and that takes some time (although it could probably be made faster). And it is exactly the routes compilation that is slow on boot time. We can easily reproduce it by adding the following to our router:<\/p>\n

\r\nFoo::Application.routes.draw do\r\n  1000.times do |index|\r\n    resources :\"posts#{index}\"\r\n  end\r\nend\r\n<\/pre>\n

This took 55 seconds on my machine using REE which is quite close to the value that he showed on his benchmark.<\/p>\n

Again, benchmarking code is important, but more important is to correctly interpret the results. In his example, it is likely that most of Rails booting time is spent on compiling the routes and the benchmark just shows how good different Ruby implementations are in handling all these regular expressions.<\/p>\n

Wrapping up<\/h3>\n

Much more interesting benchmarks for Rails boot time would actually be performed in production environment, which actually has to load all the code inside the app folder and compile the routes file. Regardless, developers starting new applications should always be skeptical about choosing a Ruby implementation based on other application’s benchmarks<\/strong>.<\/p>\n

When starting out a new application, any Ruby implementation should suit you just fine unless you have a stronger constraint (like Java integration). Once your application starts to grow and you want to evaluate how well it performs in different implementations, you should do your own benchmarks and see how it goes. In any case, don’t jump into conclusions. If you need to investigate deeper, each implementation has its own sets of benchmarking and profiling tools that may help you really understand what is actually slow and how to improve it.<\/p>\n

I also want to thank ruby-prof<\/a> authors and maintainers, for such an amazing tool, and Yehuda Katz<\/a>, who helped me profile a demo Rails application in order to write this detailed response.<\/p>\n

And you? Have you done benchmarks in your applications and found any interesting data you would like to share?<\/p>\n","protected":false},"excerpt":{"rendered":"

Erik DeBill has put two interesting benchmarks on his blog. The first one compares the performance of different Ruby implementations in Rails development mode while the second compares their performance in Rails boot time. If you haven’t read them yet, please do it now. Benchmarking code is an important practice, but it can be misleading … \u00bb<\/a><\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[1],"tags":[141,7],"aioseo_notices":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/1868"}],"collection":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/comments?post=1868"}],"version-history":[{"count":16,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/1868\/revisions"}],"predecessor-version":[{"id":1892,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/1868\/revisions\/1892"}],"wp:attachment":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/media?parent=1868"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/categories?post=1868"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/tags?post=1868"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}