Trivia Question and a Free Book

I’d like to share a fun little JRuby trivia question, and I’ll give a
free copy of my new book, “Deploying with JRuby”, to the first person
who answers it correctly:

So here’s the question:

I have the following program, which calculates fibonacci numbers:

puts Benchmark.measure { fibonacci(30) }
puts Benchmark.measure { fibonacci(30) }

When I run it on MRI, everything seems normal. But when I run it on
JRuby I get the following output:

0.329000 0.000000 0.329000 ( 0.273000)
0.084000 0.000000 0.084000 ( 0.085000)

Why is the second run of fibonacci numbers dramatically faster than the
first?

Hi Joe,

Is it because we’re seeing the JVM warming up and optimizing the byte
code? Or are you looking for something more specific?

Your book looks really interesting. I hadn’t heard about it before.
Thanks for the heads up.

Denny

On Mar 9, 2012, at 9:09 AM, Joe K. wrote:

I’d like to share a fun little JRuby trivia question, and I’ll give a
free copy of my new book, “Deploying with JRuby”, to the first person
who answers it correctly:

So here’s the question:

I have the following program, which calculates fibonacci numbers:

require ‘benchmark’

def fibonacci(n)
if n < 2
n
else
fibonacci(n - 2) + fibonacci(n - 1)
end
end

puts Benchmark.measure { fibonacci(30) }
puts Benchmark.measure { fibonacci(30) }

When I run it on MRI, everything seems normal. But when I run it on
JRuby I get the following output:

0.329000 0.000000 0.329000 ( 0.273000)
0.084000 0.000000 0.084000 ( 0.085000)

Why is the second run of fibonacci numbers dramatically faster than the
first?

I thought it was the JIT, but disabling that still gives a
speedup:

~:$ jruby -Xcompile.mode=OFF fib.jruby.rb
0.642000 0.000000 0.642000 ( 0.580000)
0.387000 0.000000 0.387000 ( 0.386000)

But I’m sure there are enough regulars here who can give
better guesses than me :slight_smile:

I’m going to give the free book to Dennis, since the JVM is indeed
warming up and optimizing things at runtime. Dennis, I’ll send you some
details in a separate email.

I think Charlie’s (slightly old) blog post describes why you still see
some speed up with compile mode off (thanks for pointing that out Dick):

Thanks for playing!

If you’d like to learn more about my book, I made a screencast of how to
get a Rails app running on JRuby and Trinidad:

-joe

On Mar 10, 2012, at 10:14 AM, Joe K. wrote:

I’m going to give the free book to Dennis, since the JVM is indeed warming up
and optimizing things at runtime. Dennis, I’ll send you some details in a
separate email.

If I run your original I get.

Mac-User:~ mjh$ jruby fib.rb
0.630000 0.000000 0.630000 ( 0.256000)
0.134000 0.000000 0.134000 ( 0.134000)

Clearly showing a much slower first run.

I change it to…

require ‘benchmark’

x = 1
x = x + 1

def fibonacci(n)
if n < 2
n
else
fibonacci(n - 2) + fibonacci(n - 1)
end
end

puts Benchmark.measure { fibonacci(30) }
puts Benchmark.measure { fibonacci(30) }

Thinking maybe the minor changes at the start of the script will absorb
the initial ‘warming up’ hit and the lag will disappear from the
benchmark numbers.
I then get…
Mac-User:~ mjh$ jruby fib.rb
0.290000 0.000000 0.290000 ( 0.245000)
0.137000 0.000000 0.137000 ( 0.137000)
Mac-User:~ mjh$ jruby fib.rb
0.390000 0.000000 0.390000 ( 0.344000)
0.137000 0.000000 0.137000 ( 0.137000)

Maybe no longer ‘much’ slower but still the first run is still some
slower.
Possibly suggesting a certain amount of JRuby ‘warming up’ (loading)
time still had to occur?

These runs are all pretty short, so there’s going to be a lot of
variability even at steady-state.

In general, you’ll see two bumps: one from JRuby and one from the JVM.
If you’re running JRuby in interpreted mode, you’ll only see the JVM
bump.

Your couple lines at the top may do a tiny bit of warmup for the
interpreter (in interpreted mode) and the + operation, but probably
not enough to really be noticeable. More likely is that your runs of
this new benchmark are loading code and JVM that the OS has already
cached, so there’s a little less lag in getting going.

The usual recommendation when benchmarking straight-line performance
is to run things to a steady state and then throw out the early
results, since they’ll reflect filesystem caching, CPU ramping up
speed (on systems where it might throttle to save power),
startup-time, and early JIT and GC overhead.

Don’t worry too much about the first number :slight_smile:

  • Charlie

On Mar 10, 2012, at 2:12 PM, Charles Oliver N. wrote:

Don’t worry too much about the first number :slight_smile:

Later runs within the same jvm instance can run into later gc concerns
though skewing benchmark runs?

More likely is that your runs of
this new benchmark are loading code and JVM that the OS has already
cached, so there’s a little less lag in getting going.

I rebooted figuring that would clear these temporary caches? And tried
again. Without changes it seems you are probably right. The first run is
slowest even without changes.
Too bad, I was happy with my ‘improvement’.