More on Ruby’s GC & Patching
Last time we covered Ruby’s garbage collector configuration, optimizing it’s settings with Rails needs in mind. There is still something we can do to improve it’s performance: patching. Even after configuring it right, it’s behavior is still inappropriate in some cases. We can solve that by simply changing it’s behavior on some situations, making it a more robust and rails-oriented garbage collector.
Some of Rails features were not developed with Ruby’s architecture in mind. They can be optimized by applying a few patches to your application. These will make your Rails application use memory and cpu more efficiently thus making Ruby’s GC much lighter since it runs less often. Some of these modifications don’t come in the form of a patch but after knowing what to avoid you’ll be able fine-tune your application’s code.
We should also watch out for memory usage. Some helpers use a lot of memory to run and consequently make Ruby’s GC kick in more often. Besides all this, there are still some helper methods that are painfully slow. We’ll get to that in a minute.
Patching Ruby’s GC
In addition to configuring Ruby’s GC you should also patch it. Stefan Kaes’s Railsbench provides some scripts which make benchmarking your Rails applications really easy and straightforward. It also provides a patch for Ruby’s garbage collector which provides some analysis functionality but also brings some speed improvements by improving defined sections of it’s code.
BigDecimal comparisons
If you accidentally compare a BigDecimal with a Boolean you’ll be throwing a method_missing call and consequent exception catches for each of these comparisons. It can slow your application if you do it very often. You can prevent this situations by never comparing an integer with a Boolean. More permanent would be changing the BigDecimal class inserting the following code:
alias_method :eq_without_boolean_comparison, :== def eq_with_boolean_comparison(other) return false if [FalseClass, TrueClass].include? other.class eq_without_boolean_comparison(other) end alias_method :==, :eq_with_boolean_comparison
This way Rails will return false if you happen to compare a BigDecimal with a Boolean. Problem solved. According Guerrilla’s Benchmarks, copying 120 tasks in Accunote spends less 100-120ms in these comparisons. Great improvement there.
My changes were made in Rails 2.3.4. I actually didn’t look into older versions but the mentioned changes and consequent benefits should also be applicable.
Replace link_to_function
Some of Rails helper methods are heavy and sometimes it’s worth avoiding them. The mentioned helper, link_to_function, is one of these.
<a href="#" onclick="...">link</a>
Use regular expressions or your editor to replace link_to_function calls with the code above. Again, Guerrilla’s Benchmarks in Accunote showed that this changes made the render take less 100-110ms. It’s crucial to apply this to render-intensive views.
Avoid ActionView Helpers with the form something_tag
Rails provides many ActionView Helpers and they can often be very handy but whenever you can, don’t use them. They have an associated performance cost. The benchmark I mentioned before took less 40-50ms by replacing
text_field_tag 'foo'
calls with:
<input id="foo" name="foo" type="text" />
They were able to cut another 110-150 milliseconds by replacing
image_tag 'bar'
calls with:
<img src="/images/bar" alt="Bar" />
There are many more examples of helpers you could eliminate by replacing them with their HTML form. On heavy views where many ActionView helpers are being called this procedure can dramatically reduce the rendering time.
has_and_belongs_to_many
No one should be using this method nowadays. It’s deprecated, has a few bugs and it’s incredibly slow. Replace all occurrences of this statement in your code with:
has_many :foo, :through => :bar
You’ve just gained a performance boost in all operations involving that model. This is even more important for those of you who still use <=2.2.1, since add_multiple_associated_save_callback method does
association.select { |record| record.new_record? }
and this call chews up a lot of memory since it loads all objects involved in a many-to-many association into memory.
This type of call is present in many areas of ActiveRecord’s code and there’s no ideal fix to date. We’ll have to bear with it making our applications slower until some genius finds a way of eradicating it from the code.
Final notes
Patching Ruby’s GC is imperative. It will allow you to decently profile your application, find it’s weak spots and, at the same time, make it run more efficiently with Rails.
Some helpers should be avoided. Although I said this many times, it is only valid when you need to increase your application’s performance. For most small projects it doesn’t make a significant difference. When you’re dealing with a lot of users or your application has some intensive views you might want to consider running a few benchmarks to see if all these modifications are worth.