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 ?
each should be faster than map since the former does not modify/create anything while the latter does.
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.
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.
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.
It seems the cost is added by:
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
each
is a method, and it's implemented using in C using a while
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With