Living on the edge (of Rails) #8 - the code optimization edition
Here's this week's update of the important changes that have been happening on edge Rails. There were lots of performance patches this week, credit mostly due to the guys from Acunote and their latest blog post on optimizing Rails.
This week’s report covers changes from 11 Feb 2008 to 17 Feb 2008 (the day the corresponding Rails Envy podcast was recorded).
ActiveRecord::Base#attributes-related optimizations
The ActiveRecord::Base#attributes_before_typecast no longer clones attribute unnecessarily.
Related changeset: http://dev.rubyonrails.org/changeset/8858
ActiveRecord::Base#attributes now no longer supports options. The ActiveRecord::Base#attributes method used to support :only and :except options, which are actually unnecessary since it returns a Hash. You can use Hash#slice or Hash#except (both ActiveSupport core extensions) directly on the hash.
Related changeset: http://dev.rubyonrails.org/changeset/8863
ActiveRecord associations optimized by avoiding named block arguments
Alexander Dymo is at it again optimizing ActiveRecord performance, this time by avoiding passing named block arguments. Turns out that it is faster and more memory efficient in Ruby to pass blocks like this:
def foo
bar { |*block_args| yield(*block_args) if block_given?
end
instead of passing the block around like so:
def foo(&&block)
bar(&block)
end
Occurrences of the more expensive way of passing blocks have been replaced in the ActiveRecord associations code so associations are lighter to use.
For a more detailed write-up, check out Alexander's blog entry.
Related changeset: http://dev.rubyonrails.org/changeset/8865
ActiveRecord associations optimized by using symbol callbacks instead of string callbacks
Again from Alexander Dymo, this patch also improving ActiveRecord association performance. Internally, ActiveRecord associations code uses a lot of string callbacks (e.g. before_save "some_ruby_code_to_eval"). It turns out that that is significantly more expensive compared to simply using Module#define_method because when the callback string is evaluated, the binding needs to be passed along. Passing the binding takes up memory (which is bad, of course).
The internal associations code has been changed to use that define_method now, and if you're using a lot of associations, you would save megabytes of memory! (Alexander's blog post has more detailed benchmarks.)
Remember, when writing your own before and after filters, not to use a string that needs to be evaled!
Related changeset: http://dev.rubyonrails.org/changeset/8867
Improve ActiveRecord::Base#save performance by avoiding repeated calls to ActiveRecord::Base#connection
Another Alexander Dymo performance patch. This particular patch improves performance by getting the connection object just once and calling it directly, instead of needlessly calling several times. This should improve ActiveRecord::Base#save performance.
Related changeset: http://dev.rubyonrails.org/changeset/8871
ActiveRecord associations now support the :readonly option
ActiveRecord::Base#find supports the :readonly option to retrieve records that were read-only (i.e. you couldn't save them). Now you can do the same for associations, like so:
has_many :comments, :readonly => true
This allows you to protect associated records from being saved.
Related changeset: http://dev.rubyonrails.org/changeset/8864
String#squish and String#squish! to remove consecutive chunks of whitespace
A String#squish extension has been added that trims whitespace from both ends of a string, and compressing consecutive whitespace groups within the string into 1 space each.
For example,
@description = %{ String#squish() allows me to write long strings
inside my Ruby code, without having to worry about indentation,
the page margin, spaces, tabs or newlines. "double" or 'single'
quotes are both fine, and #{interpolation} works. }.squish
Related changeset: http://dev.rubyonrails.org/changeset/8878
As always, let me know of any inaccuracies or any suggestions you may have in the comments, see you guys next week!