The remainder of this chapter is a collection of miscellaneous performance tips and solutions to common problems. If you have specific trouble, the Rails wiki (http://wiki.rubyonrails.com/) might help. The wiki is disorganized at times, but it has a large amount of relevant information on many topics if you are willing to search.
A large part of software development consists of selecting the right tools for the job. This encompasses not only languages but libraries, frameworks, source control, databases, servers, and all of the other tools and materials that go into a completed application.
Sometimes the best way to solve a problem is not to have a problem at all. Chances are, if you have a moderately complicated technical problem, someone else has solved it. 37signals' Basecamp takes this approach when resizing images—rather than dealing with the hassle of installing RMagick, they just shell out to ImageMagick: [60]
def thumbnail(temp, target) system( "/usr/local/bin/convert #{escape(temp)} -resize 48x48! #{escape(target)}" ) end
Part of the beauty of scripting languages is that they were designed out of necessity, so they have ways to glue disparate parts together. In addition, most scripting languages have a rich set of community-developed libraries available. Though CPAN (Perl's collection of third-party libraries) is the undisputed champion in this arena, Ruby has Rubyforge (http://rubyforge.org) and the Ruby Application Archive (http://raa.ruby-lang.org/).
Writing Ruby extensions in C used to be hard. If you wanted to rewrite performance-sensitive functions, there were many things besides the actual code that you had to deal with. Not so anymore.
Ryan Davis has unleashed an incredible tool, RubyInline, [61] for integrating C with Ruby. This tool allows you to embed C/C++ code as strings directly within an application. The strings are then compiled into native code (only to be recompiled when they change) and installed into your classes. The canonical example, the factorial function, shows just how fast and clean this can be:
require 'rubygems' require 'inline' # gem install RubyInline require 'benchmark' class Test # Standard Ruby factorial function def factorial(n) result = 1 n.downto(2) { |x| result *= x } result end # Reimplemented in C (compiled on the fly) inline do |builder| builder.c <<-EOINLINE long factorial_c(int max) { int i = max, result = 1; while (i >= 2) { result *= i--; } return result; } EOINLINE end end
We can then set up a benchmark to compare the two implementations:
t = Test.new Benchmark.bmbm do |b| b.report("Ruby factorial") do 200_000.times { t.factorial(20) } end b.report("C factorial") do 200_000.times { t.factorial_c(20) } end end
On my machine, the C implementation is extremely fast—more than 25 times the speed of the standard Ruby implementation!
user system total real Ruby factorial 2.760000 0.010000 2.770000 ( 2.753621) C factorial 0.110000 0.000000 0.110000 ( 0.104440)
The best part of RubyInline is that it keeps your code clean.
Ruby and C code addressing the same area can be intermingled, rather
than being spread across multiple files. And RubyInline handles the
type conversion for you—you can deal with ints, longs
, and char*s
, and they will automatically be
converted to and from Ruby types.
Email delivery can be a difficult and aggravating aspect of a deployed application. The SMTP protocol was not designed to withstand the types of attacks that are being directed at the mail system today, and so delivering mail can be a more complicated process than it may seem.
One common problem is that email delivery via SMTP is quite slow, on the average. In addition, it is an unknown; the time it takes to send one email is highly variable. Even when delivering to an SMTP relay on the local network (which is a good idea for high-volume sites), SMTP delivery is slow.
To counteract this slowness, it is usually desirable to decouple
the email sending from the web request/response cycle. It makes sense
to allow the user to continue working, even if the server is still
trying to send email in the background. One option is to simply
fork
off a separate OS process, or
use a separate interpreter thread (via Thread.new
with a block) to send email
asynchronously. However, this solution does not scale well, as you
must handle any concurrency issues that arise on your own. In
addition, you have overhead from starting a new thread or process on
each piece of mail. For high-volume mail situations, you want a mailer
daemon running a tight loop that can send mail without having to start
a worker process.
The scalable option is the Robot Co-op's ar_mailer. [62] This little library uses the data-base as an outgoing
mail spool. When mail is to be sent, rather than delivering it
externally, Rails just dumps it into the database. The separate
ar_sendmail
process picks it up and
sends it along. This way, the application does not get backed up
because of slow SMTP performance. ar_sendmail
can be run periodically (from
cron) or continuously, as a daemon.