ghivert
ghivert

Reputation: 548

Ruby each vs while loop performance

trying to resolve a basic problem of algorithm in Ruby, and testing performance.

Just in case, the algorithm aims to find the smallest positive number that is evenly divisible by all of the numbers from 1 to 20. Here's the code :

def remainder(number) # with while
  divisor = 2
  while divisor < 21
    return false unless number % divisor == 0
    divisor += 1
  end
  true
end

def remainder(number) # with each
  (2..20).each do |divisor|
    return false unless number % divisor == 0
  end
  true
end

number = 180_000_000
while number < 10_000_000_000
  if remainder number
    puts "#{number}"
    break
  end
  number += 1
end

On my computer, with while version, Ruby takes around 10 seconds, with each version, it takes between 70 and 80 seconds to resolve. Code does the exact same thing, gives same result. Why such a difference in performance ?

Upvotes: 12

Views: 6206

Answers (2)

Idan Arye
Idan Arye

Reputation: 12603

each is a method, and it's implemented using while in C using a for loop, which (in C) does the same thing you while loop does. Internally it needs to hold a counter, initialize it to 2 and increment it up to 20 - the same thing your while version needs to do. But the each version also has the overhead of creating a function(the block), sending it to the each method, and calling it on every iteration of the for loop inside the each method's implementation. All this requires extra work from the computer which makes the code slower.

Upvotes: 3

Wand Maker
Wand Maker

Reputation: 18762

It seems the cost is added by:

  1. Creation of enumerator for range object (2..20)
  2. Invocation of block in the each

Here is a benchmark

require 'benchmark'

c = 100_000
Benchmark.bm(7) do |x|
  x.report("range - 1 :") { c.times { (2..20) } }
  x.report("range - 2 :") { c.times { (2..20).each } }
  x.report("range - 3 :") { c.times { (2..20).each { |x| x } } }
end

Sample output of above is:

              user     system      total        real
range - 1 :  0.000000   0.000000   0.000000 (  0.006004)
range - 2 :  0.031000   0.000000   0.031000 (  0.026017)
range - 3 :  0.125000   0.000000   0.125000 (  0.122081)
[Finished in 0.4s]

As can be seen, creation of Range object is not a problem, but creating an enumerator for it adds time, and passing a block to that iterator and executing some code, adds further cost.

Compared to this, the while loop implementation is doing primitive operations. Hence, is faster.

Please note that for loop will perform as badly as each, as it is more or less equivalent to each implementation

Upvotes: 6

Related Questions