Routes recognition in rails and why speed matters
Posted by oleganza, Thu Jan 17 06:34:00 UTC 2008
Rails routes are slow now. On every single request, each pattern in a list is tested. It impacts all of your actions and makes 10..30% of the response time.
Recently, in a separate project FastRouting I've proved, that routes can be recognized about 8.8 times faster, than in Rails 2.0.2.
The main idea is to not to check every single pattern, but thoughtfully move through the input path doing as little matching as possible. More of that, checking is done in a generated code (tree of if's and elsif's).
However, it is currently very hard to port simple routing engine to ActionPack, so i have done an independent optimization using ActionPack capabilities. The original problem is that we go through all the routes:
routes:
input = /videos/123
/posts, :method => :get
/posts, :method => :post
/posts/:id, :method => :get
/posts/:id, :method => :put
/posts/:id, :method => :delete
/posts/new, :method => :get
/posts/:id/edit, :method => :get
/users
/users/...
... 100 patterns skipped ...
/videos/:id <- here it is!
If you have 10 first-level resources with average 10 routes (including subresources), you need to perform 10x10 = 100 checks. Actually, if your input is definitely does not contain "/posts" prefix, you may skip all the "/posts/..." patterns. This leads us to approximately 9 times performance boost in our imaginary experiment.
I've posted a patch #10835 implementing this idea.
On vkadre.ru with a bunch of restful routes, it shows 2..3 times route recognition boost. You may try it on your routeset using rake task (thanks to Rick Olson):
06:33 technoweenie git://activereload.net/route_benchmarks.git 06:34 technoweenie put that in vendor/plugins and run 'rake routes:benchmarks:recognition'
Note 0. Actual speedup depends on the form and the size of your routeset. On a single pattern in a routeset there will be definitely no speedup, but a 20% recognition performance drop instead. Optimization is based on skipping large sublists of patterns. When there are no lists only thing you get is an overhead of additional datastructures.
Note 1. Speedup depends on a set of requests. If the majority of the requests are matched by the first set of rules, there won't be any speedup. In other case, when requests are matched somewhere at the end of routes.rb, you get the highest performance boost.
Note 2. More optimization is possible, since only the first-level prefixes are considered currently. If you do effective skipping in nested resources, you may get up to 4 times faster recognition. To get 8.8x as i announced above, we have to significantly refactor actionpack routing.rb.
Note 3. It would be great if you try this patch, run technoweenie's benchmark before and after and drop results into Rails trac comments.
UPDATE.
Now it does 6.4x speed up using a segment tree scanning technique borrowed from FastRouting. Check this out and have a nice day!
UPDATE 2:
This has been applied to Ruby on Rails trunk.