Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 ?

like image 255
ghivert Avatar asked Sep 17 '15 10:09

ghivert


People also ask

Which is faster for each or map?

each should be faster than map since the former does not modify/create anything while the latter does.

Does while loop take more time than for?

The main reason that While is much slower is because the while loop checks the condition after each iteration, so if you are going to write this code, just use a for loop instead.

Is while loop faster than for loop in R?

for loops are fast. What you do inside the loop is slow (in comparison to vectorized operations). I would expect a while loop to be slower than a for loop since it needs to test a condition before each iteration. Keep in mind that R is an interpreted language, i.e., there are no compiler optimizations.

How do you break out of a loop in Ruby?

In Ruby, we use a break statement to break the execution of the loop in the program. It is mostly used in while loop, where value is printed till the condition, is true, then break statement terminates the loop. In examples, break statement used with if statement. By using break statement the execution will be stopped.


2 Answers

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

like image 114
Wand Maker Avatar answered Nov 08 '22 17:11

Wand Maker


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.

like image 20
Idan Arye Avatar answered Nov 08 '22 17:11

Idan Arye