Ruby Speed Question

Wrote my first Ruby program recently for a class assignment where we had
to examine the speed of binary search on various array sizes in 3
different languages. After a little debugging, I managed to get the code
working, but the difference in run-time between this and the other 2
languages is significant enough that I'm wondering if I did something
wrong.

This takes 13-14 seconds total, while Java runs in just under a quarter
of a second and C# runs in well under a hundredth of a second. I'm sure
some of the slowdown for Ruby is that I'm doing it in JRuby on NetBeans,
but even running it through a command prompt version of Ruby only
knocked a second or two off the total runtime.

Ruby code is below, Java & C# code are functionally identical.

# recursive binary search
# array = the array to be searched
# target = what to look for
# first = the first index of the range
# last = the last index of the range
# returns the index of target value, or -1 if not found
def search(list, target, first = 0, last = list.length-1)
  return -1 if first>last # basis (not found)
  mid = (first+last)/ 2
  if list[mid]==target # basis (mid is target)
    mid
  elsif target<list[mid] # recur on left half
    search(list, target, first, mid-1)
  else # recur on right half
    search(list, target, mid+1, last)
  end
end

# main method, tests binary search speed on arrays of varying sizes
# for each array size, program does the following:
# 1) fills array with even numbers (index times two)
# 2) performs 500,000 unsuccessful searches (odd numbers only)
# 3) reports total time & average time per search
# 4) brings the array out of scope to remove it from memory
if __FILE__ == $0
  puts "Data Structures & Algorithms - Assignment 1 - Problem 5 -
Ruby\n\n"
  out_a = "Performing 500k searches on an array of length"
  out_b = "required"
  out_c = "seconds,\n an average of"
  out_d = "nano-seconds per search."
  size = 32
  while size < 530000
    list = Array.new
    i = 0
    while i<size # fills the array with even numbers
      list[i] = 2*i
      i+=1
    end
    j = 1
    check = 0
    start = Time.now
    while j<1000000 # search for odd numbers
      r = search(list,j)
      check -= r
      j+=2
    end
    elapsed = Time.now - start # elapsed time in seconds
    if check!=500000
      puts "ERROR! Successful search! checksum = #{check}"
    end
    puts "#{out_a} #{size} #{out_b} #{elapsed} #{out_c} #{elapsed*2000}
#{out_d}"
    size *= 4
  end
end

···

--
Posted via http://www.ruby-forum.com/.

Java and C# are statically typed and compiled (well... more so than Ruby,
anyway). If your main use case is algorithms, those languages are a better
choice because they will much more performant (Fortran or C probably being
the best choice for such a use case). Though a decent compromise might be
Python, which has a pleasant developer experience like Ruby, but also offers
things like primitives, and has some nice libs for scientific use.

Some reasons differences between the Ruby and Java versions are that Ruby
doesn't have primitives, so all those numbers are objects (ie using Java's
Integer rather than int). Also, if you're using primitive Arrays in the
other languages, that can have an impact, as Ruby's arrays are more like
Java's ArrayLists. Also, the way you check your conditions isn't very
efficient (ie its' more likely to be less / greater than the target than
equal to the target, so by rearranging your checks, you can reduce the run
time -- though obviously not the time complexity).

Anyway, if your goal is just to look at time complexities, then Ruby is
fine, but you aren't really taking advantage of what it has to offer :slight_smile: Some
simple things like:

Array initialization could be done like this
  Array.new(size) { |index| 2 * index }

Target iteration could be done like this
  (1..1_000_000).step 2 do |target|
    check -= search(list, target)
  end

You could put the search method directly on the arrays (some oppose monkey
patching, but I think this is an okay use case. A good compromise would be
creating a module with this method, then add it only to the arrays you want
to binary search)
  class Array
    def binary_search(target, first = 0, last = length-1)
      return -1 if first > last
      mid = (first+last) / 2
      return binary_search(target, first, mid-1) if target < self[mid]
      return binary_search(target, mid+1, last) if target > self[mid]
      mid
    end
  end

The string thing is really confusing, you can just do it like this:
  puts "Performing 500k searches on an array of length #{size} required
#{elapsed} seconds,"
  puts " an average of #{elapsed*2000} nano-seconds per search"

Of course, this is just scratching the surface, this really isn't Ruby's
primary domain, so it hasn't got much opportunity to shine in this case.

···

On Sun, Sep 18, 2011 at 10:51 AM, Kevin Anon <oblivious.sage@gmail.com>wrote:

Wrote my first Ruby program recently for a class assignment where we had
to examine the speed of binary search on various array sizes in 3
different languages. After a little debugging, I managed to get the code
working, but the difference in run-time between this and the other 2
languages is significant enough that I'm wondering if I did something
wrong.

This takes 13-14 seconds total, while Java runs in just under a quarter
of a second and C# runs in well under a hundredth of a second. I'm sure
some of the slowdown for Ruby is that I'm doing it in JRuby on NetBeans,
but even running it through a command prompt version of Ruby only
knocked a second or two off the total runtime.

Ruby code is below, Java & C# code are functionally identical.

# recursive binary search
# array = the array to be searched
# target = what to look for
# first = the first index of the range
# last = the last index of the range
# returns the index of target value, or -1 if not found
def search(list, target, first = 0, last = list.length-1)
return -1 if first>last # basis (not found)
mid = (first+last)/ 2
if list[mid]==target # basis (mid is target)
   mid
elsif target<list[mid] # recur on left half
   search(list, target, first, mid-1)
else # recur on right half
   search(list, target, mid+1, last)
end
end

# main method, tests binary search speed on arrays of varying sizes
# for each array size, program does the following:
# 1) fills array with even numbers (index times two)
# 2) performs 500,000 unsuccessful searches (odd numbers only)
# 3) reports total time & average time per search
# 4) brings the array out of scope to remove it from memory
if __FILE__ == $0
puts "Data Structures & Algorithms - Assignment 1 - Problem 5 -
Ruby\n\n"
out_a = "Performing 500k searches on an array of length"
out_b = "required"
out_c = "seconds,\n an average of"
out_d = "nano-seconds per search."
size = 32
while size < 530000
   list = Array.new
   i = 0
   while i<size # fills the array with even numbers
     list[i] = 2*i
     i+=1
   end
   j = 1
   check = 0
   start = Time.now
   while j<1000000 # search for odd numbers
     r = search(list,j)
     check -= r
     j+=2
   end
   elapsed = Time.now - start # elapsed time in seconds
   if check!=500000
     puts "ERROR! Successful search! checksum = #{check}"
   end
   puts "#{out_a} #{size} #{out_b} #{elapsed} #{out_c} #{elapsed*2000}
#{out_d}"
   size *= 4
end
end

--
Posted via http://www.ruby-forum.com/\.

This general question has been answered quite a few times. Some of these links are a little out of date now but the gist is correct. BTW, JRuby is probably the fastest current runtime and the one with the best chances of reaching Java-like performance. Running it with NetBeans isn't slowing you down though you may want to see if you can configure NetBeans to run JRuby with the --server switch (more aggressive JIT'ing by hotspot).

http://carboni.ca/blog/p/Another-Cool-Reason-Ruby-is-Slow-implicit-super

You can google for more links if you like.

That said, Ruby is probably never going to be as fast as Java or C#. It has advantages beyond speed such as no compile -> link -> run cycle, better ways to express thought (IMHO), a more English-like readability, etc.

I rewrote your example to take advantage of a few Ruby idioms. Your original was essentially a straight port of the C version (minus the memory management). This version is actually a little bit slower on JRuby (the calls to #not_found?, #found? and #left? add about 20% overhead). Interestingly, this rewrite is *faster* when run on Rubinius than your original code. That's one of the nice things about this ecosystem... there are 3 good choices for Ruby runtimes all with the various strengths & weaknesses.

class BinarySearch

  # main method, tests binary search speed on arrays of varying sizes
  # for each array size, program does the following:
  # 1) fills array with even numbers (index times two)
  # 2) performs 500,000 unsuccessful searches (odd numbers only)
  # 3) reports total time & average time per search
  # 4) brings the array out of scope to remove it from memory
  def initialize
    puts "Data Structures & Algorithms - Assignment 1 - Problem 5 - Ruby\n\n"

    @size = 32
    @list =
  end

  def run
    while @size < 530_000
      @list.clear
      populate_with_even_numbers

      @check = 0
      elapsed_time = measure_elapsed_time do
        search_for_odd_numbers
      end

      puts "ERROR! Successful search! checksum = #{@check}" if checksum_failure?

      output_results(@size, elapsed_time)
      @size *= 4
    end
  end

  # recursive binary search
  # array = the array to be searched
  # target = what to look for
  # first = the first index of the range
  # last = the last index of the range
  # returns the index of target value, or -1 if not found
  def search(target, first, last)
    return -1 if not_found?(first, last) # basis (not found)

    middle = (first + last) / 2
    if found?(middle, target)
      middle
    elsif left?(middle, target)
      search(target, first, middle - 1)
    else # recur on right half
      search(target, middle + 1, last)
    end
  end
  
  def not_found?(first, last)
    first > last
  end
  
  def found?(middle, target)
    @list[middle] == target
  end
  
  def left?(middle, target)
    target < @list[middle]
  end

  def populate_with_even_numbers
    # fills the array with even numbers
    @size.times { |i| @list[i] = 2 * i }
  end

  def measure_elapsed_time(&blk)
    start = Time.now
    yield
    Time.now - start
  end

  def search_for_odd_numbers
    1.step(1_000_000, 2) do |j|
      r = search(j, 0, @list.length - 1)
      @check -= r
    end
  end

  def checksum_failure?
    500_000 != @check
  end

  def output_results(size, elapsed_time)
    puts "Performing 500k searches on an array of length " + size.to_s + " required " +
    elapsed_time.to_s + " seconds, an average of " + (elapsed_time * 2000).to_s +
    " nano-seconds per search"
  end
end

if __FILE__ == $0

  b = BinarySearch.new
  b.run

end

To my eye this reads much better and expresses the execution a bit more clearly.

We hope you stick around and learn more about the language.

cr

···

On Sep 18, 2011, at 10:51 AM, Kevin Anon wrote:

Wrote my first Ruby program recently for a class assignment where we had
to examine the speed of binary search on various array sizes in 3
different languages. After a little debugging, I managed to get the code
working, but the difference in run-time between this and the other 2
languages is significant enough that I'm wondering if I did something
wrong.

This takes 13-14 seconds total, while Java runs in just under a quarter
of a second and C# runs in well under a hundredth of a second. I'm sure
some of the slowdown for Ruby is that I'm doing it in JRuby on NetBeans,
but even running it through a command prompt version of Ruby only
knocked a second or two off the total runtime.

Thanks for your comments, Chuck & Josh!

The purpose of the assignment is to examine the timing results and
explain why they differ between programming languages, whether they
follow the theoretical timing complexity (mostly they do, other than the
size 128 array, which in both Java and Ruby seems to often be either too
high or too low), and if not why it might be offset from the theoretical
complexity.

My goal isn't to make the Ruby code fast, but simply to compare 3
different languages. I was just a little concerned that I'd done
something wrong in my Ruby implementation when it was almost two orders
of magnitude slower than Java. From what you guys said, however, it
sounds like I'm just comparing marathon times for 2 marathon runners and
a sprinter.

I really appreciate the suggestions regarding improvements in the code,
but the program has to be as close to identical as possible across the
three languages, barring only syntactical differences. I'll definitely
look your suggestions over to improve my understanding of Ruby, however.

Josh, thanks in particular for pointing out why Ruby would be so much
slower than Java and C#.

···

--
Posted via http://www.ruby-forum.com/.

I ran a speed test on a simple 'for' loop and found ruby does close to
12 million loops per second - that is fast enough for me with this very
simple test

Joe

···

--
Posted via http://www.ruby-forum.com/.

Hey Kevin what school do you attend?

···

--
Posted via http://www.ruby-forum.com/.

How are you controlling for startup time? JRuby's startup has always
been heavy.

This is actually a problematic approach to comparison. Different
languages are optimizable for different algorithmic approaches, and what
seems the same between two languages based on similar syntax may actually
be semantically different so that behind the scenes you end up comparing
apples and oranges. A deeper understanding of the differences between
language implementations is needed than just the existence of similar
syntactical forms if you want to perform an apples to apples comparison.

In general, Ruby will tend to be slower than Java and C#, but how much
slower depends on the specific form of your source code and the specific
Ruby implementation you use -- and there may even be times when a Ruby
operation might be faster than an equivalent in Java or C#. As I
indicated above, though, some of that variance will depend on whether
what looks the same syntactically might actually be very different behind
the scenes. For instance, in some respects a Ruby symbol is more like a
Java string primitive than a Ruby string (and working with strings in
Ruby is typically much slower for execution time than working with
symbols).

···

On Mon, Sep 19, 2011 at 05:18:06AM +0900, Kevin Anon wrote:

I really appreciate the suggestions regarding improvements in the code,
but the program has to be as close to identical as possible across the
three languages, barring only syntactical differences. I'll definitely
look your suggestions over to improve my understanding of Ruby, however.

--
Chad Perrin [ original content licensed OWL: http://owl.apotheon.org ]

This is not 100% true. While they are from a language user's
perspective under the hood there are some optimizations especially for
Fixnums. This still leaves overhead for method invocation but memory
usage is very low and there is no GC overhead.

Kind regards

robert

···

On Sun, Sep 18, 2011 at 8:42 PM, Josh Cheek <josh.cheek@gmail.com> wrote:

Some reasons differences between the Ruby and Java versions are that Ruby
doesn't have primitives, so all those numbers are objects (ie using Java's
Integer rather than int).

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Hello

I am looking for a good candidate on ruby and rails.

Do you have any names for me?

Thanks !

Jeanne

···

-----Original Message-----
From: Joe Collins [mailto:joec_49@hotmail.com]
Sent: 20 September 2011 16:44
To: ruby-talk ML
Subject: Re: Ruby Speed Question

I ran a speed test on a simple 'for' loop and found ruby does close to
12 million loops per second - that is fast enough for me with this very
simple test

Joe

--
Posted via http://www.ruby-forum.com/.

Search for CVs on-line or sign-up for jobs by email at:
www.progressiverecruitment.com

This electronic transmission is confidential and intended solely for the
addressee(s.) If you are not an intended addressee, you must not disclose, copy,
distribute or take any action in reliance upon this transmission. If you have
received this transmission in error, please notify Progressive Recruitment
Limited.

You can find our privacy statement at
http://www.progressiverecruitment.com/en/page/privacy_policy If you do not want
to receive electronic mail from us about our services, or would like to confirm
or qualify how we hold your personal data, please send a request via email to
data-audit@progressiverecruitment.com
http://www.progressiverecruitment.com/en/page/registration_details

I've had people say this to me in the past, but I don't understand how this
can be, given that I can set instance variables on Fixnums:

1.class # => Fixnum
1.instance_variable_set :@abc, 12
1.instance_variable_get :@abc # => 12

···

On Mon, Sep 19, 2011 at 2:48 AM, Robert Klemme <shortcutter@googlemail.com>wrote:

On Sun, Sep 18, 2011 at 8:42 PM, Josh Cheek <josh.cheek@gmail.com> wrote:

> Some reasons differences between the Ruby and Java versions are that Ruby
> doesn't have primitives, so all those numbers are objects (ie using
Java's
> Integer rather than int).

This is not 100% true. While they are from a language user's
perspective under the hood there are some optimizations especially for
Fixnums. This still leaves overhead for method invocation but memory
usage is very low and there is no GC overhead.

Kind regards

robert

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

>
> > Some reasons differences between the Ruby and Java versions are that Ruby
> > doesn't have primitives, so all those numbers are objects (ie using
> Java's
> > Integer rather than int).
>
> This is not 100% true. While they are from a language user's
> perspective under the hood there are some optimizations especially for
> Fixnums. This still leaves overhead for method invocation but memory
> usage is very low and there is no GC overhead.

On YARV, method invocation is inlined for common operations on
basic types, including simple Fixnum arithmetic (see insns.def).

I've had people say this to me in the past, but I don't understand how this
can be, given that I can set instance variables on Fixnums:

1.class # => Fixnum
1.instance_variable_set :@abc, 12
1.instance_variable_get :@abc # => 12

Ruby stores ivars for immutable object types (e.g. Fixnum/Symbol) in
external hashes (see generic_ivar_* in variable.c for ruby/trunk).

···

Josh Cheek <josh.cheek@gmail.com> wrote:

On Mon, Sep 19, 2011 at 2:48 AM, Robert Klemme > <shortcutter@googlemail.com>wrote:
> On Sun, Sep 18, 2011 at 8:42 PM, Josh Cheek <josh.cheek@gmail.com> wrote: