9

I know that for i in arr slightly differs from arr.each scope and everyone keeps saying that iterators are preferrable, but I wonder if there is any case when cycle is preferrable and why it is (since iterators are more idiomatic)?

p0deje
  • 3,903
  • 1
  • 26
  • 37
  • 3
    When prototyping in Ruby with intent to speed up with C extensions, I find it helps to use more C-like idioms, there is less to convert. But probably not the answer you are looking for . . . – Neil Slater Nov 25 '13 at 16:51
  • 2
    This comment is not intended as an answer, merely giving some additional information. According this [this answer](http://stackoverflow.com/a/3294543/877472), both `for` and `each` use `.each` in the background. The main difference (according to [this answer from the same question](http://stackoverflow.com/a/3294561/877472) ) is how the iterator variable is scoped. Ultimately, other than the Neil Slater's post, it sounds like there may not be any technical reason to prefer one over the other except. – Paul Richter Nov 25 '13 at 17:24

2 Answers2

11

TL;DR

  • Use for loop for performance in Ruby 1.8
  • Use for loop to standards in existing projects
  • Use each loop to minimize side effects
  • Prefer each loop.

each minimizes side effects

The primary difference between for and each is scoping.

The each function takes a block. Blocks create a new lexical scope. This means that any variables declared within the scope of the function will no longer be available after the function.

[1, 2, 3].each do |i|
  a = i
end

puts a
# => NameErrror: undefined local variable or method `a' for main:Object

Whereas:

for i in [1, 2, 3]
  a = i
end

puts a
# => 3

Thus, using the each syntax minimizes the risk of side effects.

Determing exit point?

This said, there are special instances where the for loop may be helpful. Specifically, when finding out where a loop exited.

for i in 1..3
  a = i

  break if i % 2 == 0
end

puts a
# => 0

There is a better way to do this though:

a = (1..3).each do |i|
  break i if i % 2 == 0
end

Each is faster (in Ruby 2.0)

Benchmark.bm(8) do |x|
  x.report "For" do
    max.times do
      for i in 1..100
        1 + 1
      end
    end
  end

  x.report "Each" do
    max.times do
      (1..100).each do |t|
        1+1
      end
    end
  end
end

Ruby 2.0

               user     system      total        real
For        6.420000   0.000000   6.420000 (  6.419870)
Each       5.830000   0.000000   5.830000 (  5.829911)

Ruby 1.8.6 (Slower Machine)

              user     system      total        real
For      17.360000   0.000000  17.360000 ( 17.409992)
Each     21.130000   0.000000  21.130000 ( 21.250754)

Benchmarks 2

If you read the comment trail, there is a discussion about the speed of creating objects in for vs each. The link provided had the following benchmarks (although, I have cleaned up the formatting and fixed the syntax errors).

b = 1..10e5

Benchmark.bmbm (10) do |x|
  x.report "each {}" do
    b.each { |r| r + 1 }
  end

  x.report "each do end" do
    b.each do |r|
      r + 1
    end
  end

  x.report "for do end" do
    for r in b do
      r + 1
    end
  end
end

Ruby 2.0

                  user     system      total        real
each {}       0.150000   0.000000   0.150000 (  0.144643)
each do end   0.140000   0.000000   0.140000 (  0.143244)
for do end    0.150000   0.000000   0.150000 (  0.147112)

Ruby 1.8.6

                  user     system      total        real
each {}       0.840000   0.000000   0.840000 (  0.851634)
each do end   0.730000   0.000000   0.730000 (  0.732737)
for do end    0.650000   0.000000   0.650000 (  0.647186)
Dan Grahn
  • 9,044
  • 4
  • 37
  • 74
  • 1
    I understand this. I also know that internally Ruby converts for to each. But I wonder if there is still any case when for would be used instead of each. – p0deje Nov 25 '13 at 17:48
  • I found that when performance is an issue the `for` loop can be beneficial. Creating objects in Ruby is expensive and can cause an performance hit in loop with many iterations. – thomthom Nov 25 '13 at 17:57
  • @thomthom Can you provide benchmarks which show that this is the case? – Dan Grahn Nov 25 '13 at 17:58
  • @p0deje as explained in the answer it is only useful when you don't want the variable defined in the loop to be limited to the scope of the loop, which you can completely almost always avoid – bjhaid Nov 25 '13 at 18:03
  • @p0deje After thinking, I added an example of when it would be useful to use. – Dan Grahn Nov 25 '13 at 18:06
  • @screenmutt From this thread: http://sketchucation.com/forums/viewtopic.php?f=180&t=25305#p221022 referring to this link http://blog.shingara.fr/each-vs-for.html – thomthom Nov 25 '13 at 18:07
  • Note that SketchUp has been using Ruby 1.8 and the performance results might be different in newer versions. – thomthom Nov 25 '13 at 18:08
  • 1
    @thomthom I just added the benchmarks. It does look like Ruby 1.8 has a faster run time for `for` loops, but that has been fixed. – Dan Grahn Nov 25 '13 at 18:21
  • Ah - yea, that matches what I just tested as well. Things have been turned around. – thomthom Nov 25 '13 at 18:25
2

I have written a few plugins for SketchUp's Ruby API and I found that when iterating large collections (of geometry entities) I would get better performance with a for in loop over an each block.

I believe this to be because the for in loop doesn't create it's local scope and objects are reused instead of being created for every iteration as it would in the each loop.


EDIT: The speed gain depends on Ruby version. Using the test snippet used in this article http://blog.shingara.fr/each-vs-for.html:

Ruby 1.8.6:

              user     system      total        real
For      14.742000   0.000000  14.742000 ( 14.777000)
Each     18.190000   0.000000  18.190000 ( 18.194000)

Ruby 2.0.0

               user     system      total        real
For        5.975000   0.000000   5.975000 (  5.990000)
Each       5.444000   0.000000   5.444000 (  5.438000)

Things has greatly improved since the old 1.8.6. (Though, SketchUp extension developers still need to optimize against this version.)

thomthom
  • 2,854
  • 1
  • 23
  • 53
  • 2
    1.8.6 fell off supported status a long time ago. It was also a great deal slower, so its use for a comparison is dubious. Perhaps the final version of 1.8.7 would be more reasonable, but 1.9.3 would make more sense. – the Tin Man Nov 25 '13 at 20:03
  • 1
    True, if you had control over the Ruby platform. But if you write extensions for SketchUp you have to deal with Ruby 1.8.6. – thomthom Nov 26 '13 at 10:43