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