Ruby vs Java: Why world is going to end faster with Java?
Asked Answered
K

1

7

I have tested recursive method execution speed with classic example of Honoi Tower.

First in Java than JRuby and Ruby with different no. of plates:

package com.example;

public class Hanoi {

  public static void main(String[] args) {      
    int [] plates = {25, 26, 27, 28, 29, 30, 31, 32};
    for(int i = 0; i < plates.length; i++){
        long start = System.currentTimeMillis();
        doTowers(plates[i], 'A', 'B', 'C');
        System.out.println(System.currentTimeMillis() - start);
    }
  }

  public static void doTowers(int topN, char from, char inter, char to) {
    if (topN == 1) {
      //NOP
    } else {
      doTowers(topN - 1, from, to, inter);
      doTowers(topN - 1, inter, from, to);
    }
  }

}

And results are:

Java(millis)   JRuby(sec)   Ruby(sec)    Ruby(sec)   Ruby(sec)   
java 7         jruby-1.7.9  jruby-1.7.9  ruby-2.1.3  ruby-2.1.3 {tailcall_optimization: true}

364            0.269        3.395        6.160       5.515
380            0.321        6.288        12.401      11.082
1349           1.173        13.462       25.497      22.661
2328           1.25         25.714       50.223      44.494
4674           4.73         51.159       101.825     89.22
4995           5.014        103.252      200.308     177.034
18633          18.637       208.356      411.667     357.561
19978          20.927       421.86       805.138     711.872

Looks like running on java and jruby has the same performance.

  1. Is it all about JVM? Or does ruby uses only single core of machine?
  2. What is the reason for this nonlinear performance loss in Ruby?
  3. What is wrong with ruby 2?

EDITED

Added results of Ruby 2.1.3 with { tailcall_optimization: true }. As you can see now it faster then with default false option.

And another question:

  1. Why Ruby code runs times faster on jruby (which currently loads ruby 1.9) than on ruby 2.1.3? Does ruby code compiles as well and runs on jvm?

The Ruby and JRuby implementetion are below:

class Hanoi

  def do_towers(top_n, from, inter, to)
    if top_n == 1
      #NOP
    else
      do_towers top_n - 1, from, to, inter
      do_towers top_n - 1, inter, from, to
    end
  end
end

[25, 26, 27, 28, 29, 30, 31, 32].each do |plate|
  start = Time.now
  HanoiRb.new.do_towers plate, 'A', 'B', 'C'
  puts Time.now - start
end

JRuby:

include Java
$CLASSPATH << 'lib'

Hanoi = JavaUtilities.get_proxy_class('com.example.Hanoi')

[25, 26, 27, 28, 29, 30, 31, 32].each do |plate|
  start = Time.now
  Hanoi.doTowers(plate, 'A'.to_java.toCharArray[0], 'B'.to_java.toCharArray[0], 'C'.to_java.toCharArray[0])
  puts Time.now - start
end
Kenyon answered 27/12, 2014 at 20:22 Comment(4)
I am not in any way qualified to answer this question, but a bit of a run in I had with another programmer ended with him showing me this and me losing a debate. theregister.co.uk/2012/11/08/twitter_epic_traffic_saved_by_javaBritishism
@Britishism thank you. Twitter happy end story proves Java purposes. Twitter gained its goals with Ruby and joined new realm, the one where Java rules. But I am interested in this particular case.Kenyon
What's up with the "end of the world" title?Wounded
@KyleStrand ...when the last move of the puzzle (plate) will be completed, the world will end.Kenyon
U
3

Is it all about JVM?

that is what you results suggest. The JVM does heavily optimise the code.

Or does ruby use only single core of machine?

Your Java program appears to only use one core as well, so that wouldn't matter.

What is the reason for this nonlinear performance loose in Ruby?

The performance in Ruby looks linear with the amount of work required to move all the plates. The Java ones are more surprising.

The JVM doesn't do tail call optimisation so it would be interesting if you did this in code.

Unpile answered 27/12, 2014 at 20:28 Comment(6)
"The performance in Ruby looks linear with the amount of work" but could you please explain nonlinear performance loss(in dynamic) comparing to JVM with increasing the amount of work? Thanks!Kenyon
@SergiiBrytiuk each extra depth of plates takes twice as many moves so you would expect the pattern that Ruby demonstrates. The Java one appears to be in jumps which suggests some "interesting" performance optimisation are at play.Unpile
@PeterLawrey The jumps are every other increment (this really pops out if you chart them on a log graph). I would guess that what's happening is that each time you need another word of RAM to fit the int[] plates (which on a 64 bit machine is every other increment, since ints are 32 bits), the code gets slower. For instance, n=26 requires 13 words, while n=27 and n=28 require 13 words (well, 12.5 for n=27, but you can't have half a word). So there's a jump from n=26 to n=27, but not from n=27 to n=28. Basically, Java is fast enough that these small affects are measurable.Dredger
See this image for an illustration. The left axis contains the two JVM run times on a log graph; the right axis is CEILING(plates.length / 2) (which is the number of words you need). You can see the lines seem to move more or less together, except for one "blip" on the Java at n=28, which could just be noise (from a GC, compilation, other activity on the computer, etc).Dredger
By the way: the IBM J9 JVM does perform Tail Call Optimization.Botryoidal
@JörgWMittag I can not understand why is all about this TCO. Looks like method I've called is tail recursive. But nor Java supports TCO(my java at least), neither Ruby (by default). I would like to know why is Ruby n times slower. As Peter Lawrey said - "The JVM does heavily optimise the code". And he also mentioned in comments "The Java one appears to be in jumps which suggests some "interesting" performance optimisation are at play". So can you help me to get what is this "interesting" thing is? Thank you.Kenyon

© 2022 - 2024 — McMap. All rights reserved.