for or each?

K

Ken Bloom

Phlip said:
Does 'for' reevaluate its range after each tick? That would give
'for' a single technical advantage over .each, in the very rare
chance you need that.

No:

class Numeric
def foo
puts "foo"
self
end
end
for i in 1..10.foo
puts i
end

prints:

foo
1
2
3
4
5
6
7
8
9
10
=> 1..10
 
X

Xavier Noria

T24gU3VuLCBTZXAgMjEsIDIwMDggYXQgMjo1NCBBTSwgSm9lIFfDtmxmZWwgPGpvZUB0YWxraG91
c2UuY29tPiB3cm90ZToKCj4gUnVieSAxLjg2IG91dHB1dDoKPiBSZWhlYXJzYWwgLS0tLS0tLS0t
LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KPiBmb3IgbG9vcCAgICAg
ICAgIDIuODQwMDAwICAgMC4wNDAwMDAgICAyLjg4MDAwMCAoICAyLjg4MDQzNykKPiBmb3IgbG9v
cCAyICAgICAgIDEuNjYwMDAwICAgMC4wMDAwMDAgICAxLjY2MDAwMCAoICAxLjY2MTIyOSkKPiBl
YWNoICAgICAgICAgICAgIDEuNzUwMDAwICAgMC4wMTAwMDAgICAxLjc2MDAwMCAoICAxLjc1NTUw
MikKPiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSB0b3RhbDogNi4z
MDAwMDBzZWMKPgo+ICAgICAgICAgICAgICAgICAgICAgdXNlciAgICAgc3lzdGVtICAgICAgdG90
YWwgICAgICAgIHJlYWwKPiBmb3IgbG9vcCAgICAgICAgIDIuMzAwMDAwICAgMC4wMDAwMDAgICAy
LjMwMDAwMCAoICAyLjMwNzU2NikKPiBmb3IgbG9vcCAyICAgICAgIDEuNjYwMDAwICAgMC4wMTAw
MDAgICAxLjY3MDAwMCAoICAxLjY2NjIxOCkKPiBlYWNoICAgICAgICAgICAgIDEuNzYwMDAwICAg
MC4wMDAwMDAgICAxLjc2MDAwMCAoICAxLjc2MDM1NikKCgpSZWxhdGl2ZSBzcGVlZCBiZXR3ZWVu
IGZvciAoMikgYW5kIGVhY2ggYXJlIHNpbWlsYXIgaW4gSlJ1YnkuIFRoYXQgMS45CnRoaW5nIGlz
IGludHJpZ3VpbmcuCg==
 
C

Charles Oliver Nutter

Phlip said:
'for' is arguably more readable. And it's not a performance issue - I
suspect the opcodes will be the same. It is very much a technical issue.

For is generally faster because it doesn't instantiate a new scope for
each loop. It shares the same scope as its containing block of code,
where each creates a new scope each time the block is called. I'm
reasonably sure this applies to all Ruby versions too.

- Charlie
 
C

Charles Oliver Nutter

Joe said:
You can try my test if you like. I haven't checked it carefully. = But=20
it does seem like 'each' is much faster under both ruby 1.86 and ru= by=20
1.9. The code is below.

Odd, now that I try it, each *is* faster than for. I'm going to have =
to=20
investigate.

JRuby:
user system total real
for loop 0.334000 0.000000 0.334000 ( 0.334033)
each 0.236000 0.000000 0.236000 ( 0.235695)

Ruby 1.8.6:
user system total real
for loop 1.050000 0.010000 1.060000 ( 1.079704)
each 0.950000 0.010000 0.960000 ( 0.982576)

Ruby 1.9:
user system total real
for loop 0.910000 0.010000 0.920000 ( 0.933510)
each 0.420000 0.000000 0.420000 ( 0.431548)

- Charlie
 
J

Joe Wölfel

It's hard to predict performance in advance. I think it's gotten =20
harder as processors have become more complicated and there are more =20
alternatives for running code. I'm just guessing here, but maybe =20
blocks are so critical to ruby performance that 'each' has benefited =20
from attempts to optimize the performance of blocks in general.

-Joe
 
S

Sylvain Joyeux

'for' is arguably more readable. And it's not a performance issue - I
suspect the opcodes will be the same. It is very much a technical issue.
Nope. each { } needs one new scope for each iteration while "for ... in"
explicitely uses the parent scope... In the end, you create with #each
as many objects as there are in your collection - which can be a huge
performance hit in some cases. That's why I personally use "for ... in" in
performance-critical parts of my code, to avoid unnecessary GC.


Sylvain
 
X

Xavier Noria

Nope. each { } needs one new scope for each iteration while "for ... in"
explicitely uses the parent scope... In the end, you create with #each
as many objects as there are in your collection - which can be a huge
performance hit in some cases.

In what sense does #each create objects? Assuming a block with just
one parameter, you mean there's a new reference to existing objects
per iteration? May that impact that much GC?
 
B

Brian Candler

Phlip said:
Tx that's why I said 'for' can be more readable - even though I know
nobody in
person aware of its existence.

FWIW, the skeleton code which Rails generates uses the 'for' loop.

$ rails wombat
$ cd wombat
$ script/generate scaffold flurble
$ cat app/views/flurbles/index.html.erb
...
<% for flurble in @flurbles %>
<tr>
..
</tr>
<% end %>
...
 
B

Brian Candler

Sylvain said:
Nope. each { } needs one new scope for each iteration

By 'scope' do you mean 'stack frame'?

Take this trivial example:

def each1
yield 1
yield 2
yield 3
end
each1 { :dummy }

The only 'objects' being created here are stack frames, by the yield
statements calling the block. To make it more explicit,

def each2(&blk)
blk.call(1)
blk.call(2)
blk.call(3)
end
each2 { :dummy }

Now, if you are arguing that the above code creates three objects which
need to be garbage-collected later, then you're also arguing that the
sequence

foo(1)
foo(2)
foo(3)

creates three objects which need to be garbage-collected, and therefore
that the loop

for i in (1..3)
foo(i)
end

also creates three garbage objects.

I don't believe that's the case. I would imagine that the stack runs as,
well, a stack. (It's not quite that simple when you get into creating
closures of course, but if you call a closure 1000 times, you're not
creating 1000 new closures)

Am I missing something?

Finally, I tried some simple measurements.

$ time ruby -e 'a = (1..5_000_000).to_a; a.each { :dummy }'
$ time ruby -e 'a = (1..5_000_000).to_a; for i in a; :dummy; end'

Under ruby 1.8.6p114, I find the first is about 5% faster.

Under ruby 1.8.4 (Ubuntu Dapper), I find the first is about 25% faster.

This is on relatively old Pentium machines though.
 
B

Brian Candler

Sylvain said:
Nope. each { } needs one new scope for each iteration while "for ... in"
explicitely uses the parent scope... In the end, you create with #each
as many objects as there are in your collection

P.S. Here's a simple experiment, and I can't see any of these
dark-matter objects that you talk about.

def countobj
count = 0
ObjectSpace.each_object(Object) { count += 1 }
count
end

def foo
:dummy
end

puts "#{countobj} objects"
GC.disable
(1..1_000_000).each { foo }
puts "#{countobj} objects"
 
S

Sylvain Joyeux

P.S. Here's a simple experiment, and I can't see any of these
dark-matter objects that you talk about.

def countobj
count = 0
ObjectSpace.each_object(Object) { count += 1 }
count
end

def foo
:dummy
end

puts "#{countobj} objects"
GC.disable
(1..1_000_000).each { foo }
puts "#{countobj} objects"
Thanks but nope...

each_object *specifically* filters out objects that are internal to the
interpreter, therefore you don't see those here. For that, you actually
need a better object-counting setup. See Ruby patches for one that I
submitted. I hope that the "new Ruby GC profiler" that have been
included in 1.9 will provide the same amount of information.

Sylvain
 
S

Sylvain Joyeux

By 'scope' do you mean 'stack frame'?
creates three objects which need to be garbage-collected, and therefore
that the loop

for i in (1..3)
foo(i)
end

also creates three garbage objects.
Yes. Except that

collection.each do |obj|
foo(obj)
end

creates twice the amount of

for obj in collection
foo(obj)
end

which is what we are trying to compare here.
I don't believe that's the case. I would imagine that the stack runs as,
well, a stack. (It's not quite that simple when you get into creating
closures of course, but if you call a closure 1000 times, you're not
creating 1000 new closures)

Am I missing something?
Yes. I don't know for the current version of the 1.9 VM, but currently
the interpreter does not 'reuse' stack frames, and therefore you *do*
create one object per new scope. To cut down the discussion,
ObjectSpace#each_object does *not* give you those, so you can't count
them with it.
Finally, I tried some simple measurements.

$ time ruby -e 'a = (1..5_000_000).to_a; a.each { :dummy }'
$ time ruby -e 'a = (1..5_000_000).to_a; for i in a; :dummy; end'

Under ruby 1.8.6p114, I find the first is about 5% faster.

Under ruby 1.8.4 (Ubuntu Dapper), I find the first is about 25% faster.
Interesting. I have the same results, but (on my machine) the following
is even slower:
$ time ruby -e 'a = (1..5_000_000).to_a; a.each { |i| :dummy }'
 
B

Brian Candler

Sylvain said:
To cut down the discussion,
ObjectSpace#each_object does *not* give you those, so you can't count
them with it.

OK, I see that certain objects are not yielded, including T_SCOPE.

On the other hand, observe that the following program doesn't leak
memory:

def foo
:dummy
end

GC.disable
(1..10_000_000).each { |i| foo }
puts `ps auxwww | grep ruby | grep -v grep`
puts "Press enter"
STDIN.gets

On the two machines I tried the RSS is 3MB, regardless of how big I make
the loop. (That's 1.8.4 stock Ubuntu Dapper, and 1.8.6p114 compiled from
source)

So are you sure a scope is created every time round the loop - not just
on the first invocation?

The following program *does* consume memory:

def foo
:dummy
end

GC.disable
(1..1_000_000).each { |x|
(1..1).each { |y| foo }
}
puts `ps auxwww | grep ruby | grep -v grep`
puts "Press enter"
STDIN.gets

That is: I'm happy to accept that every time the inner loop starts, it
creates a new scope (since the block is a closure with a different value
of x bound to it)

But if I change the inner loop to

(1..10).each { |y| foo }

the memory consumption is the same. So whether a block is invoked once
or 10 times makes no difference to memory usage.
 
N

namekuseijin

Come on! Is it so hard to realize that foo.each {|i| ...} is used
more simply because it fits better the ruby all-OO mindset? Like
Foo.new instead of the more common new Foo?

In search of purity, everything is an object and all actions are
method calls. It feels very much like Scheme and its (sweet)
obsession for doing everything out of functions and function
application...

Rubinists are used to postfix notation just as much as Forth users or
Lispers to prefix...
 
T

Tucano

You should look at the Matz book The Ruby Programming Language page 137:

external or internal iterators
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Staff online

Members online

Forum statistics

Threads
474,175
Messages
2,570,942
Members
47,489
Latest member
BrigidaD91

Latest Threads

Top