From: "M. Edward (Ed) Borasky" Date: 2006-11-09T00:36:37+09:00 Subject: Re: Ruby performanmce improvements Michael Selig wrote: > I know you guys are in the middle of YARV stuff, but I thought you might be > interested in some performance improvements (around 20%) I made to Ruby in > my spare time recently. > > One afternoon when I had a bit of spare time I decided to compile Ruby with > profiling (C profiling that is), to try to see where it was spending most of > its time. Also I needed some benchmarks to test with, so, for want of > anything better, I took some ruby scripts from the "Computer Language > Shootout" (http://shootout.alioth.debian.org/). > > I found 3 main areas which I then focused on: > 1) rb_call() especially rb_call0() > 2) Loops & rb_yield(), especially rb_yield_0() > 3) The large number of recursive calls to rb_eval() > > So addressing these one at a time: > 1) rb_call() > I found that many of these calls are to CFUNC (internal C) methods such as > Fixnum#+, most of which are simple, and don't access anything other than > their parameters. However, in order to execute these methods, ruby is still > doing quite a bit of work in rb_call0(), such as setting up a stack frame > etc, which is usually not used. So I made an optimisation that if a CFUNC > method is being called, and no block is being passed to it, then it bypasses > stack frame creation. (I also had to make a small hack to "backtrace()" to > handle errors in this case). > > 2) Loops & rb_yield() > Most of Ruby's loops end up calling rb_yield() for each iteration. However > much of the work that rb_yield_0 does each time can be often be done once at > the start of the loop: > - Pushing frame, block, vars, class etc > - Freeing/creation of dynamic variables (instead they can be initialized > to NIL each time, unless the context has been saved) > Also loops done by rb_iterate() (via an IFUNC stub) can be simplified > further to reduce the overhead of "double-yield". > > 3) rb_eval() calls > I simply inlined the code for common, simple nodes such as: NODE_LIT, > NODE_LVAR etc in certain places within rb_eval(), which resulted in a large > reduction in the number of calls to rb_eval(). > > After applying these mods, I was able to achieve a performance improvement > averaging 20% (ie: 20% less User CPU time) under Linux/386 in the "shootout" > benchmarks. I know these are not representative of Ruby programs in general, > but I had to use something that was simple to time. > > My mods are based on Ruby 1.8.4. > > I am not 100% sure that my mods haven't broken something - especially in the > Garbage Collector (which I haven't really tried to understand), but it seems > to work fine here. I did not touch the parser at all. > > Please let me know if you'd like me to post a patch. If I get some more > spare time, I'd be happy to look at the Ruby 2.0 code to see if similar > optimizations can be made. > > Mike > Could you run my matrix benchmark against these and post the results here? The benchmark is at http://rubyforge.org/cgi-bin/viewvc.cgi/MatrixBenchmark/?root=cougar I got a 4X improvement by running with YARV the last time I tested this. Also, did you use "gprof"? How do your profiles compare with the ones I posted for the matrix benchmark?