Ruby scaling up to multiple CPUs
April 28, 2009
I wrote a quick micro benchmark to test out ruby threads. Apparently ruby can’t make use of multiple CPUs with it’s threading implementation. I guess you have to resort to forking to scale up to multiple cpu cores while using mri ruby. However, there is an alternative solution, just use JRuby. JRuby utilizes all cores when running the benchmark.
Ruby 1.8.7 - Compiled with SunCC SSX0903 (-xO5 -fast -xipo)
Total number of insane floating point divisions in 10 seconds is 5969107
Ruby 1.9.1 / Compiled with GCC 4.3.2 (-O3 -fomit-frame-pointer)
Total number of insane floating point divisions in 10 seconds is 8596894
Ran as: jruby –fast cpuMax.rb
177% increase in performance
JRuby 1.3-dev / JDK7 b56
Total number of insane floating point divisions in 10 seconds is 15915896
Ran as: jruby –fast -J-Djruby.compile.mode=JIT -J-Djruby.jit.threshold=0 -J-server cpuMax.rb
374% increase in performance
JRuby 1.3-dev / JDK7 b56
Total number of insane floating point divisions in 10 seconds is 28334441
Looking at mpstat, I can see the MRI ruby implementation is not utilizing all 4 cores.
Ruby 1.8.7 MRI CPU minf mjf xcal intr ithr csw icsw migr smtx srw syscl usr sys wt idl 0 828 0 13 428 204 487 26 93 16 0 1903 4 5 0 91 1 2682 0 3 39 2 280 32 81 12 2 1189 13 2 0 85 2 1902 0 0 34 11 259 16 57 13 0 1094 11 3 0 86 3 1017 0 3 192 150 111 34 38 8 0 676 92 2 0 6 CPU minf mjf xcal intr ithr csw icsw migr smtx srw syscl usr sys wt idl 0 2 0 5 422 205 388 11 75 5 0 627 4 1 0 95 1 161 0 13 20 2 196 11 61 8 0 1405 2 2 0 96 2 292 0 6 32 15 272 10 57 7 0 700 2 1 0 97 3 0 0 0 108 65 74 35 28 3 0 346 99 0 0 1
Now here is the JRuby implementation.
CPU minf mjf xcal intr ithr csw icsw migr smtx srw syscl usr sys wt idl 0 229 0 202 755 193 1977 365 329 94 1 2869 90 2 0 8 1 328 0 86 371 1 1817 226 303 125 0 2809 86 2 0 12 2 294 0 128 326 0 1771 248 287 109 0 2290 88 2 0 10 3 320 0 172 402 62 1848 246 241 116 0 2238 86 3 0 11 CPU minf mjf xcal intr ithr csw icsw migr smtx srw syscl usr sys wt idl 0 317 0 297 700 192 2047 323 341 136 1 2819 89 2 0 9 1 320 0 61 279 2 1611 134 195 130 0 1960 85 2 0 13 2 288 0 235 379 0 1941 291 299 115 0 2462 87 2 0 11 3 308 0 78 316 43 1688 142 159 104 0 1706 85 2 0 13
I think I will stick to JRuby for production use.
#!/usr/bin/ruby require 'thread' threads = [] counter = 0 mutex = Mutex.new 4.times do threads << Thread.new { x=0 y=0 time=Time.new while 1 do if Time.new - time >= 10 then break else x=1.00/24000000000.001 y+=1 end end mutex.synchronize { counter+=y.to_i } } end threads.each { |t| t.join } puts "Total number of insane floating point divisions in 10 seconds is "+counter.to_s
ruby 1.8? can you benchmark ruby 1.9?
David, the author is correct to use 1.8.7 since 1.9 is quite useless for Rails production today. Even with 2.3, the suggested Ruby is 1.8.7.
ruby 1.8.7 (2008-08-11 patchlevel 72) [i686-darwin9]
Total number of insane floating point divisions in 10 seconds is 6075996
jruby 1.1.6 (ruby 1.8.6 patchlevel 114) (2009-04-04 rev 6586) [i386-java]
Total number of insane floating point divisions in 10 seconds is 6920745
ruby 1.9.1p0 (2009-01-30 revision 21907) [i386-darwin9]
Total number of insane floating point divisions in 10 seconds is 7866221
@David Make sure you set 1:1 thread to cpu core. My benchmark was run on a quad core xeon. Hence the 4 spawned threads.
Ruby 1.9 should also perform poorly, it still has the giant lock in that interpreter that prevents ruby from running truly concurrent threads.
@another David, 1.9 isn’t useless in Rails today, it works perfectly (all tests pass)…the problems arise in 3rd party gems, which may not be compatible.
ruby 1.8.7 (2008-06-20 patchlevel 22) [i686-darwin9]
Total number of insane floating point divisions in 10 seconds is 3667928
jruby 1.1.5 (ruby 1.8.6 patchlevel 114) (2009-03-31 rev 6586) [i386-java]
Total number of insane floating point divisions in 10 seconds is 4973125
ruby 1.9.1p0 (2009-01-30 revision 21907) [i386-darwin9.6.0]
Total number of insane floating point divisions in 10 seconds is 5460718
Erm, is anyone really suprised by this? It’s 2009 and people still don’t know that ruby 1.8 uses green threads? So did Java until recently, actually.
While ruby 1.9 uses native threads, it still uses a Global Interpreter Lock (GIL) for the sake of backwards compatibility for C extension developers, which prevents it from running multiple threads at once. However, fixing this is on the roadmap for 1.9.
Indeed, fully understood. My point is just to put it in the perspective we’ll need in the next few months, i.e. 1.9 vs jruby, not 1.8.
Too many Davids here, btw. 🙂
if Time.new – time >= 10
You’re creating a new Time object on every iteration, which as well probably taking longer than the division may well synchronise on some central heap mutex. If so this would make the results invalid.
It would be a better idea if the main thread signalled or stopped the test threads after waiting for 10s.
FYI, a couple tips running JRuby:
1. pass –server for best performance; unless you’re running on 64-bit JVM, it runs in “client” mode normally, which is quite a bit slower
2. if you find we’re slower than 1.9 for straight-line perf, please file a bug (or try running longer)
3. JRuby has improved by double-digit percentages in almost every release over the past year. Run the most recent for real comparisons.
4. See http://kenai.com/projects/jruby/pages/Benchmarks for more information on how to benchmark JRuby well.
FYI, that’s “dash dash server”…I think the formatting turned it into an emdash.
And feel free to stop by #jruby on FreeNode for more discussion.
@Charles Nutter
–client performed better since I gave it no warm up time. So I just rolled with that.
@Jon
Consider that part of the benchmark. I am well aware I am initiating a new object each iteration. Everything is an object in ruby 😉
@snuxoll
I think Java has had native threads since 1.2 which is around 8 years ago .. hardly recently.
Added Ruby 1.9.1 for the hell of it. Ruby 1.9.1 was compiled with GCC 4.3.2, could not get it to compile with SunCC
Compare it with multi process KRI? 🙂
[oh and 1.9.1 is ok with rails]
=r
“@another David, 1.9 isn’t useless in Rails today, it works perfectly (all tests pass)…the problems arise in 3rd party gems, which may not be compatible.”
Then their tests are crap. ruby-1.9 & rails has a year-old show-stopping encoding bug which the ruby guys seem to be incompetent to fix.
See https://rails.lighthouseapp.com/projects/8994/tickets/2188-i18n-fails-with-multibyte-strings-in-ruby-19-similar-to-2038