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

···

--
Posted via http://www.ruby-forum.com/.

Ted Flethuseo wrote:

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

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

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 10/29/2010 4:28 PM, Ted Flethuseo wrote:

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

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:34 PM, Jeremy Bopp <jeremy@bopp.net> wrote:

On 10/29/2010 4:28 PM, Ted Flethuseo wrote:
> 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

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.

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 Fri, Oct 29, 2010 at 11:59 PM, Ammar Ali <ammarabuali@gmail.com> wrote:

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.

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.

<snip>

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 10/29/2010 4:59 PM, Ammar Ali 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

···

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

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

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:24 AM, Jeremy Bopp <jeremy@bopp.net> wrote:

On 10/29/2010 4:59 PM, Ammar Ali 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.

<snip>

> 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

I do. Thanks for the pointers.

Cheers,
Ammar

···

On Sat, Oct 30, 2010 at 12:28 AM, Jeremy Bopp <jeremy@bopp.net> 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

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 5:36 PM, Ammar Ali 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.

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:

    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

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 30.10.2010 00:47, Jeremy Bopp wrote:

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

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

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 10/31/2010 05:15 AM, Robert Klemme wrote:

On 30.10.2010 00:47, Jeremy Bopp wrote:

On 10/29/2010 5:36 PM, Ammar Ali 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.

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 Cooper wrote an article about
it recently, I think I'll go re-read it.

    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

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

···

On Sun, Oct 31, 2010 at 5:15 AM, Robert Klemme <shortcutter@googlemail.com>wrote: