The Rails routing module is mainly responsible for
-
parsing urls to turn them into (controller, action, parameter hash) tuples
-
generating urls from (controller, action, parameter hash) tuples
-
generating helper methods for
-
generating hashes for named routes (given additional parameters)
-
generating urls for named routes (given additional parameters)
-
Ever since Rails started to support routing specifications written in Ruby, both functionality and implementation become more complex with almost each new release. And the routing code wasn’t fast to boot, for sure.
Both Rails 2.1 and Rails 2.2 try to make routing faster by generating code specific to each route at runtime. This results in a speed increase for each individual routing related operation, but inflates the total code size of an application. As a consequence, garbage collection time of a MRI Ruby can be catapulted well above 100ms (this isn’t as much of a problem for jruby, as it uses a much better garbage collection algorithm, but I haven’t had a chance to try it so far).
This plugin improves the situation by generating code only for named routes helpers which are actually used by the application and making the generated code more compact.
-
Ruby
-
Rails 2.1 or Rails 2.2
Install the plugin in the plugin directory of your rails application using one of the well known methods.
Rails routing will be optimized automatically once the plugin has been installed. There is no API and there are no configuration options.
Well, that’s actually a lie. You can set an environment variable
RAILS_DEBUG_ROUTING_CODE=1
and then the plugin will write all routing related generated code to two files under /tmp:
-
generated_routing_code.rb (code generated for recognition and generation)
-
named_route_helpers_code.rb (code generated for named routes helpers)
The plugin optimizes the Rails’ routing subsystem in 2 ways:
-
Named route helpers will be generated lazily upon first use. After a helper has been generated, there is no further performance penalty for using it.
-
The generated code is more compact than the code generated by Rails (with negligible performance impact on an individual call).
Both changes aim at reducing the number of live objects after garbage collection. For MRI Ruby, this is important as it uses a simple mark and sweep garbage collector, the performance of which degrades linearly with heap size.
For a complex Rails application with a large number of named route definitions, especially whith restful routing, the reduction in heap size can be enormous. For example, I have measured one application, were 600,000 out of 1,600,000 AST nodes were eliminated by the plugin and garbage collection time was reduced by almost a factor of 2.
The best way to measure the effects the plugin on your application is to use either bleak_house or a Ruby patched with my garbage collection patches. I do, of course, recommend the second option ;-)
Get the patched Ruby from railsexpress.de/downloads/ruby186pl287.tar.gz
Unpack, cd into it and execute
autoconf configure --enable-gcdebug --prefix=<some directory> make sudo make install
add <some directory>/bin at the beginning of PATH and install all the gems you need.
Congratulations! You now have an additional version of Ruby on your system, with support for creating heap dumps from a running application. It will use somewhat more memory than a Ruby compiled without heap dump support.
You can obtain a heap dump by calling
# clean up heap GC.start # clean up potential garbage from running finalizers GC.start # dump it, including class names of heap objects GC.dump_file_and_line_info(filename="heap.dump", include_class_names=true)
The resulting dump can then be analyzed using railsbench’s new analyze_heap_dump command (get the bleeding edge gem “skaes-railsbench” fom github).
railsbench analyze_heap_dump -o wow heap.dump
will create a html visualization of the heap dump and name the file “wow.html” along with two text files listing the memory hotspots (linked from the html file).
The most accurate measurements are of course obtained from a live application server. One way way to do this is to install a signal handler to produce a heap dump whenever you feel like it.
trap("USR2"){ ... }
Another option is to create a dump at the end of running your functional tests, if you have a comprehensive test suite for your application. This avoids having to run one of your application servers with the debug version of Ruby and will create most of the lazily generated helper code.
To follow this route, add code similar to the one given below:
at_exit do GCstart; GC.start GC.dump_file_and_line_info("heap.dump", true) end
Be sure to place this code before requiring the test framework, as this installs an exit handler of its own. Adding the handler after requiring the test framework would effectively lead to dumping the heap before running the tests.
See LICENSE for license information.