Is this an effective loop

I was wondering if a loop of this sort would be
effective in ruby for a large number of elements

for i in 1..layers[0].count-1
  puts i
end

Is it equivalent in efficiency to this?

int i = 1
while i < layers[0].count-1
puts i
i+=1
end

Ted F. wrote:

while i < layers[0].count-1
puts i
i+=1
end

Don’t wonder, benchmark it[1]. My guess is that the former is more
efficient because you are only calculating layers[0].count-1 once
instead of every iteration, but you never know. Neither one is
particularly idiomatic Ruby.

-Justin

[1]
http://ruby-doc.org/stdlib/libdoc/benchmark/rdoc/classes/Benchmark.html

On 10/29/2010 4:28 PM, Ted F. wrote:

while i < layers[0].count-1
puts i
i+=1
end

I’m not sure about comparative effectiveness, but this is probably more
idiomatic:

(layers[0].count - 1).times do |i|
puts i+1
end

It’s probably also more efficient if only a bit since it does not
require the creation and evaluation of a Range object.

-Jeremy

On Fri, Oct 29, 2010 at 11:34 PM, Jeremy B. [email protected] wrote:

int i = 1
end

It’s probably also more efficient if only a bit since it does not
require the creation and evaluation of a Range object.

int i = 1? That’s not valid ruby, unless you have a method named int,
which
I doubt.

Indeed, a benchmark is the best way to find out. Here’s a quick one,
with
slight changes to your original code and no puts, and including Jeremy’s
idiomatic suggestion.

require ‘benchmark’
include Benchmark

Benchmark.benchmark do |bm|
bm.report(“for loop”) {
for i in 1…1_000_000
i * i
end
}

bm.report(“while loop”) {
i = 1
while i < 1_000_000
i * i
i+=1
end
}

bm.report(“times loop”) {
1_000_000.times do |i|
i * i+1
end
}
end

The numbers I got on my machine are:

for loop 1.210000 0.000000 1.210000 ( 1.225631)
while loop 1.460000 0.010000 1.470000 ( 1.469653)
times loop 1.980000 0.000000 1.980000 ( 1.998238)

I expected the times version to be faster too, but it looks like the for
loop is the fastest in this case.

Regards,
Ammar

On Fri, Oct 29, 2010 at 11:59 PM, Ammar A. [email protected]
wrote:

I spoke too soon, my bad. Once I made the loops do the same work inside,
the
numbers got closer to expectations.

require ‘benchmark’
include Benchmark

Benchmark.benchmark do |bm|
bm.report(“for loop”) {
for i in 1…1_000_000
i * i
end
}

bm.report(“while loop”) {
i = 1
while i < 1_000_000
i * i
i += 1
end
}

bm.report(“times loop”) {
1_000_000.times do |i|
i * i
end
}
end

for loop 1.210000 0.000000 1.210000 ( 1.217159)
while loop 1.450000 0.000000 1.450000 ( 1.459296)
times loop 1.260000 0.010000 1.270000 ( 1.259652)

That’s less surprising. The times loop is faster than before.

Regards,
Ammar

On 10/29/2010 5:20 PM, Ammar A. wrote:

I spoke too soon, my bad. Once I made the loops do the same work inside, the
numbers got closer to expectations.

I just missed that before sending my message. :wink: Still, running the
tests multiple times is necessary to get a decent idea of the
performance. Your machine could just be oddly loaded during any single
run of tests, and that will skew your measurements. Of course, my set
of 5 measurements isn’t /much/ better, but you get the idea.

-Jeremy

On Sat, Oct 30, 2010 at 12:24 AM, Jeremy B. [email protected] wrote:

while loop 1.460000 0.010000 1.470000 ( 1.469653)
require ‘benchmark’

   i += 1

while loop 1.750000 0.000000 1.750000 ( 1.860000)
for loop 1.687000 0.000000 1.687000 ( 1.902000)

-Jeremy

I see. I excluded the i += 1 from the for and times loops because that
is
done automatically. Adding them seemed to add work that was not
necessary
for those constructs, and IMHO, make the benchmark inaccurate.

Regards,
Ammar

On Sat, Oct 30, 2010 at 12:28 AM, Jeremy B. [email protected] wrote:

I just missed that before sending my message. :wink: Still, running the
tests multiple times is necessary to get a decent idea of the
performance. Your machine could just be oddly loaded during any single
run of tests, and that will skew your measurements. Of course, my set
of 5 measurements isn’t /much/ better, but you get the idea.

-Jeremy

I do. Thanks for the pointers.

Cheers,
Ammar

On 10/29/2010 5:36 PM, Ammar A. wrote:

I see. I excluded the i += 1 from the for and times loops because that is
done automatically. Adding them seemed to add work that was not necessary
for those constructs, and IMHO, make the benchmark inaccurate.

That’s definitely a debatable point in this benchmark because we are
essentially doubling the number of add operations for the loops that
perform it implicitly. However, we also want to see how the choice of
looping mechanism affects the operations performed within the loop, so
making the looped operations completely identical has its merits.

Regardless, the big thing we see is that the loops all perform roughly
equivalently overall. Their relative differences in overhead will
likely be dwarfed by the looped operations in the real world.

In this case, I would choose the more idiomatic approach for the sake of
brevity, familiarity, and safety. I really hate how easy it is to have
off-by-one errors and similar problems in the more manually iterated for
and while constructs.

-Jeremy

On 10/29/2010 4:59 PM, Ammar A. wrote:

Indeed, a benchmark is the best way to find out. Here’s a quick one, with
slight changes to your original code and no puts, and including Jeremy’s
idiomatic suggestion.

The numbers I got on my machine are:

for loop 1.210000 0.000000 1.210000 ( 1.225631)
while loop 1.460000 0.010000 1.470000 ( 1.469653)
times loop 1.980000 0.000000 1.980000 ( 1.998238)

I expected the times version to be faster too, but it looks like the for
loop is the fastest in this case.

This is interesting, and in an effort to get slightly more accurate data
on this microbenchmark, I tweaked your code a bit to ensure that the
loops are performing the same work in each case. I then looped the test
5 times:

require ‘benchmark’

count = 1_000_000
5.times do
Benchmark.benchmark do |bm|
bm.report(“for loop”) {
for i in 0…count-1
i += 1
i * i
end
}

bm.report("while loop") {
  i = 0
  while i < count
    i += 1
    i * i
  end
}

bm.report("times loop") {
  count.times do |i|
    i += 1
    i * i
  end
}

end

puts
end

=>
for loop 1.656000 0.000000 1.656000 ( 1.888000)
while loop 1.750000 0.000000 1.750000 ( 1.860000)
times loop 1.735000 0.000000 1.735000 ( 1.954000)

for loop 1.984000 0.000000 1.984000 ( 1.980000)
while loop 1.625000 0.000000 1.625000 ( 1.848000)
times loop 1.813000 0.000000 1.813000 ( 1.967000)

for loop 1.797000 0.000000 1.797000 ( 1.923000)
while loop 1.687000 0.000000 1.687000 ( 1.850000)
times loop 1.938000 0.000000 1.938000 ( 1.956000)

for loop 1.687000 0.000000 1.687000 ( 1.902000)
while loop 1.813000 0.016000 1.829000 ( 1.842000)
times loop 1.718000 0.000000 1.718000 ( 1.943000)

for loop 1.782000 0.000000 1.782000 ( 1.901000)
while loop 1.734000 0.000000 1.734000 ( 1.843000)
times loop 1.734000 0.000000 1.734000 ( 1.956000)

As can be seen, there is a fair bit of variance in the benchmark. There
does not appear to be a clear winner overall.

-Jeremy

On 30.10.2010 00:47, Jeremy B. wrote:

On 10/29/2010 5:36 PM, Ammar A. wrote:

I see. I excluded the i += 1 from the for and times loops because that is
done automatically. Adding them seemed to add work that was not necessary
for those constructs, and IMHO, make the benchmark inaccurate.

That’s definitely a debatable point in this benchmark because we are
essentially doubling the number of add operations for the loops that
perform it implicitly. However, we also want to see how the choice of
looping mechanism affects the operations performed within the loop, so
making the looped operations completely identical has its merits.

I’m sorry but this is nonsense: different loop constructs are
benchmarked precisely to determine which iteration is quicker.
Incrementing the loop variable is one of the things that make up the
difference between looping constructs so there is no point in doing this
“manually” just to make loop bodies look identical.

Here’s my version

Robert@babelfish ~
$ ruby19 x.rb
Rehearsal ----------------------------------------------
for loop 2.168000 0.000000 2.168000 ( 2.211000)
while loop 1.981000 0.000000 1.981000 ( 2.015000)
times loop 2.075000 0.016000 2.091000 ( 2.150000)
------------------------------------- total: 6.240000sec

              user     system      total        real

for loop 2.106000 0.000000 2.106000 ( 2.149000)
while loop 1.919000 0.000000 1.919000 ( 1.919000)
times loop 2.012000 0.000000 2.012000 ( 2.063000)

Robert@babelfish ~
$ cat x.rb
require ‘benchmark’

count = 1_000_000

Benchmark.bmbm do |bm|
bm.report(“for loop”) {
5.times do
for i in 0…count
i * i
end
end
}

bm.report(“while loop”) {
5.times do
i = 0
while i < count
i * i
i += 1
end
end
}

bm.report(“times loop”) {
5.times do
count.times do |i|
i * i
end
end
}
end

puts

Regardless, the big thing we see is that the loops all perform roughly
equivalently overall. Their relative differences in overhead will
likely be dwarfed by the looped operations in the real world.

Agreed.

In this case, I would choose the more idiomatic approach for the sake of
brevity, familiarity, and safety. I really hate how easy it is to have
off-by-one errors and similar problems in the more manually iterated for
and while constructs.

Actually there was one in the first posting of this thread:

end
The answer is no because the second one does one less iteration. :slight_smile:

Please note also that you can use three dot ranges to exclude the end so
the first one could be rewritten as

for i in 1…layers[0].count

end

Kind regards

robert

On 10/31/2010 05:15 AM, Robert K. wrote:

perform it implicitly. However, we also want to see how the choice of
looping mechanism affects the operations performed within the loop, so
making the looped operations completely identical has its merits.

I’m sorry but this is nonsense: different loop constructs are
benchmarked precisely to determine which iteration is quicker.
Incrementing the loop variable is one of the things that make up the
difference between looping constructs so there is no point in doing this
“manually” just to make loop bodies look identical.

Yeah, I suppose you’re right now that I think about it again. Thanks
for the smack with the clue-by-four. :wink:

-Jeremy

On Sun, Oct 31, 2010 at 5:15 AM, Robert K.
[email protected]wrote:

Regardless, the big thing we see is that the loops all perform roughly

equivalently overall. Their relative differences in overhead will
likely be dwarfed by the looped operations in the real world.

Agreed.

FWIW, on mine they were not close. The while loop was much faster on
every
implementation except MRI 1.8.6 and 1.8.7

I used your benchmark, except switched bmbm with just bm, for
readability of
output (the results were not significantly different).

I don’t know how to take that, I would have expected while loop to be
significantly slower than all of the rest.

Also, interestingly, check out Macruby! Peter C. wrote an article
about
it recently, I think I’ll go re-read it.

end

The answer is no because the second one does one less iteration. :slight_smile:

Also, the top one calculates layers[0].count-1 only once. The bottom one
re-evaluates that expression every time. That makes the bottom one less
efficient (I am truly stunned that it won the benchmarks so thoroughly),
and
could also mean they behave differently depending on what you do in the
loop.

layers = [1,2,3,4,5]

for i in 1…(puts “FOR LOOP”; layers.size-1)
end

i = 1
while i < (puts “WHILE LOOP”; layers.size-1)
i+=1
end