Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Ruby1.8

Comparing Ruby and C# Performance

4.75/5 (7 votes)
8 Nov 2012CPOL3 min read 55.1K  
A quick test of Ruby performance vs. C#

Introduction

As I'm in the middle of learning Ruby and Ruby on Rails, I wanted to do a quick comparison of Ruby vs C#, knowing quite well that C# will outperform Ruby, but I wanted to get some idea by how much.  This site indicates that Ruby is about 25 times slower on average, but I wanted to see for myself.  As it turns out, Ashraff Ali Wahab had a couple days ago posted the article Eratosthenes/Sundaram/Atkins Sieve Implementation in C#, and I figured this would be a quick way to write some tests.

This is obviously not conclusive - it's a bit like comparing apples and oranges at the code level, especially since I tried to leverage the syntax of Ruby to my current level of understanding.  Also, I decided to give IronRuby a try as well, however the tests were inconclusive as the program failed to complete.

Source Code

C#

Please refer to Ashraff Ali Wahab article for the C# source code.

Ruby

Brute Force Algorithm

def BruteForce(topCandidate)
  totalCount = 1
  isPrime = true

  3.step(topCandidate, 2) do |i|
    j=3

    while j*j <= i && isPrime
      isPrime = false if i%j==0
      j += 2
    end

    isPrime ? totalCount += 1 : isPrime = true
  end

totalCount
end

Sieve of Eratosthenes Algorithm

def SieveOfEratosthenes(topCandidate)
  myBA1 = Array.new(topCandidate + 1) {true}
  myBA1[0] = myBA1[1] = false
  thisFactor = 2

  while thisFactor * thisFactor <= topCandidate do
    mark = thisFactor + thisFactor
    mark.step(topCandidate+1, thisFactor) {|n| myBA1[n] = false}

    thisFactor += 1

    while !myBA1[thisFactor] do
      thisFactor += 1
    end

  end

  myBA1.count(true)
end

Sieve of Sundaram Algorithm

def SieveOfSundaram(topCandidate)
  k = topCandidate / 2
  myBA1 = Array.new(k + 1) {true}
  myBA1[0] = myBA1[k] = false

  for i in 1..k do
    denominator = (i << 1) + 1
    maxVal = (k - i) / denominator
    i.step(maxVal+1, 1) {|n| myBA1[i + n * denominator] = false}

    # this version takes .20 seconds longer to run 1M iterations!
    # for n in i..maxVal+1 do
      # myBA1[i + n * denominator] = false
    # end
  end

myBA1.count(true) + 1
end

"Main"

def main
  max = 1000000
  startTime = Time.now()
  primes = BruteForce(max)
  endTime = Time.now()
  elapsed = endTime - startTime
  printf("Elapsed time for Brute Force : %f Primes = %d\n", elapsed, primes)

  startTime = Time.now()
  primes = SieveOfEratosthenes(max)
  endTime = Time.now()
  elapsed = endTime - startTime
  printf("Elapsed time for Sieve of Eratosthenes: %f Primes = %d\n", elapsed, primes)

  startTime = Time.now()
  primes = SieveOfSundaram(max)
  endTime = Time.now()
  elapsed = endTime - startTime
  printf("Elapsed time for Sieve of Sundaram : %f Primes = %d\n", elapsed, primes)
end

The Results

As you can see from these screenshots:

Image 1

Image 2

Ruby is:

  • about 5 times slower for the brute force algorithm
  • about 19 times slower for the Eratosthenes and Sundaram algorithms

For my purposes, that's essentially inline with the shootout website I mentioned in the Introduction.

Sadly, the IronRuby program did not complete:

Image 3

Dying on this line:

myBA1.count(true)

But the brute force algorithm was consistently almost twice as slow.

Running on a Virtual Box

I'm also running Ubuntu on Virtual Box (2GB RAM, 3 processors) and was pleased with the results:

Image 4

Only about 3 times slower!

Conclusion

While not conclusive, it was a useful exercise to go through.  Note particularly the commented out Ruby code:

i.step(maxVal+1, 1) {|n| myBA1[i + n * denominator] = false}

# this version takes .20 seconds longer to run 1M iterations!
# for n in i..maxVal+1 do
  # myBA1[i + n * denominator] = false
# end

The "for" loop version takes almost 50% longer!  That is a significant and worthwhile discovery, and it essentially makes sense -- the step function is a library implementation (and I would assume therefore compiled) whereas the for loop I would imagine is constantly being interpreted.  Still, it's a significant difference, especially considering that the block {|n| myBA1[i + n * denominator] = false} theoretically is implemented as a function call.

Also, it was disappointing that the IronRuby code failed.  I was hoping that something this "simple" would not have issues.

Lastly, please do not take this as a detraction to Ruby!  This is an amazing language and for many purposes, performance is not the most important concern - interactions with a database and network latency (if you're thinking of Ruby on Rails) will often contribute more to the perception of performance than the language performance.

Also, there appear to be some compilers available, for example Rubinius as well as The Ludicrous JIT Compiler. The former looked much too complicated to try, and the latter, Ludicrous, I did try but was not successful with the installation.  Given that the creator claims "Though still in the experimental stage, its performance is roughly on par with YARV", it doesn't seem that helpful, given that:  "Probably the most exciting and visible change in Ruby 1.9 is the addition of a bytecode interpreter for Ruby. The YARV (Yet Another Ruby VM) interpreter was integrated into the Ruby project, replacing the interpreter created by Matz (aka MRI, Matz's Ruby Interpreter)." (read here).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)