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] = 2i
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

On Sun, Sep 18, 2011 at 10:51 AM, Kevin Anon
[email protected]wrote:

but even running it through a command prompt version of Ruby only
def search(list, target, first = 0, last = list.length-1)

out_b = “required”
j = 1
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 Sep 18, 2011, at 10:51 AM, Kevin Anon wrote:

but even running it through a command prompt version of Ruby only
knocked a second or two off the total runtime.

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

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#.

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.

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 Sun, Sep 18, 2011 at 8:42 PM, Josh C. [email protected]
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

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

Josh C. [email protected] wrote:

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).

On Mon, Sep 19, 2011 at 2:48 AM, Robert K.
[email protected]wrote:

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/

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

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

Hey Kevin what school do you attend?

Hello

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

Do you have any names for me?

Thanks !

Jeanne