Is a block converted to a Proc object before yield?

Hello!

As you know, in Ruby, a block is not an object for some reasons, one of
which is performance.

def f
  yield
end

def g &blk
  blk.call
end

In g, a block is explicitly converted to a Proc object.
What about in f?
Is a block still converted to a Proc object implicitly?

If it's not converted, I understand the performance issue.
However, it's always converted to a Proc object, I don't understand why
blocks improve performance just because they are not objects.

Can somebody explain this, please?

Thanks.

Sam

···

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

Hi,

···

In message "Re: Is a block converted to a Proc object before yield?" on Fri, 30 Jun 2006 02:57:32 +0900, Sam Kong <sam.s.kong@gmail.com> writes:

Is a block still converted to a Proc object implicitly?

Not under the current implementation.

              matz.

Sam Kong wrote:

Hello!

As you know, in Ruby, a block is not an object for some reasons, one of
which is performance.

def f
  yield
end

def g &blk
  blk.call
end

In g, a block is explicitly converted to a Proc object.
What about in f?
Is a block still converted to a Proc object implicitly?

If it's not converted, I understand the performance issue.
However, it's always converted to a Proc object, I don't understand why
blocks improve performance just because they are not objects.

Can somebody explain this, please?

You've got the answer already, but it's interesting to see how much of a
difference it makes: (YMMV, of course.)

require 'benchmark'

def outer11(&bl)
  inner1(&bl)
end

def outer12(&bl)
  inner2(&bl)
end

def outer21
  inner1 {yield}
end

def outer22
  inner2 {yield}
end

def inner1(&bl)
  bl.call
end

def inner2
  yield
end

n = 100000

Benchmark.bmbm(10) do |rpt|
  rpt.report("outer11") do
    n.times {outer11{}}
  end

  rpt.report("outer12") do
    n.times {outer12{}}
  end

  rpt.report("outer21") do
    n.times {outer21{}}
  end

  rpt.report("outer22") do
    n.times {outer22{}}
  end
end

__END__

Output:

Rehearsal ---------------------------------------------
outer11 0.890000 0.010000 0.900000 ( 0.894500)
outer12 0.370000 0.000000 0.370000 ( 0.364880)
outer21 0.770000 0.000000 0.770000 ( 0.776638)
outer22 0.170000 0.000000 0.170000 ( 0.163393)
------------------------------------ total: 2.210000sec

                user system total real
outer11 0.490000 0.000000 0.490000 ( 0.491969)
outer12 0.400000 0.000000 0.400000 ( 0.396264)
outer21 0.760000 0.000000 0.760000 ( 0.764508)
outer22 0.160000 0.000000 0.160000 ( 0.161982)

···

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

Yukihiro Matsumoto wrote:

>Is a block still converted to a Proc object implicitly?

Not under the current implementation.

              matz.

I'm pretty sure that this is a very official answer...:slight_smile:

Matz, I want to take this opportunity to thank you for making me a happy
programmer with Ruby.

Sam

···

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

Joel VanderWerf wrote:

Sam Kong wrote:

  blk.call
Can somebody explain this, please?

You've got the answer already, but it's interesting to see how much of a
difference it makes: (YMMV, of course.)

Thank you for the benchmark which convinces me of blocks being more
efficient.

What a lovely community Ruby has!

Sam

···

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

Joel VanderWerf wrote:

Sam Kong wrote:
> Hello!
>
> As you know, in Ruby, a block is not an object for some reasons, one of
> which is performance.

I though this had been largely optimized. Can't ruby refrain from
instatiating the block as a proc until it is needed as such? If so then
simply calling .call on the block reference could just trigger yield.
That would give them nearly the same performance characteristics.

T.

While to anybody else this code made things clear, for me it is still
a little bit confusing:

why outer12 is performing slower than outer22?

According to prev posts, I have understood that the usage of
block.call requires a conversion to a Proc and this is slower. But in
above case where is this conversion taking place? (or simply, why is
it slower?).

thanks (and sorry to be confused),

./alex

···

--
.w( the_mindstorm )p.
---
(http://themindstorms.blogspot.com)

On 6/29/06, Joel VanderWerf <vjoel@path.berkeley.edu> wrote:

Sam Kong wrote:
> Hello!
>
> As you know, in Ruby, a block is not an object for some reasons, one of
> which is performance.
>
> def f
> yield
> end
>
> def g &blk
> blk.call
> end
>
> In g, a block is explicitly converted to a Proc object.
> What about in f?
> Is a block still converted to a Proc object implicitly?
>
> If it's not converted, I understand the performance issue.
> However, it's always converted to a Proc object, I don't understand why
> blocks improve performance just because they are not objects.
>
> Can somebody explain this, please?

You've got the answer already, but it's interesting to see how much of a
difference it makes: (YMMV, of course.)

require 'benchmark'

def outer11(&bl)
  inner1(&bl)
end

def outer12(&bl)
  inner2(&bl)
end

def outer21
  inner1 {yield}
end

def outer22
  inner2 {yield}
end

def inner1(&bl)
  bl.call
end

def inner2
  yield
end

n = 100000

Benchmark.bmbm(10) do |rpt|
  rpt.report("outer11") do
    n.times {outer11{}}
  end

  rpt.report("outer12") do
    n.times {outer12{}}
  end

  rpt.report("outer21") do
    n.times {outer21{}}
  end

  rpt.report("outer22") do
    n.times {outer22{}}
  end
end

__END__

Output:

Rehearsal ---------------------------------------------
outer11 0.890000 0.010000 0.900000 ( 0.894500)
outer12 0.370000 0.000000 0.370000 ( 0.364880)
outer21 0.770000 0.000000 0.770000 ( 0.776638)
outer22 0.170000 0.000000 0.170000 ( 0.163393)
------------------------------------ total: 2.210000sec

                user system total real
outer11 0.490000 0.000000 0.490000 ( 0.491969)
outer12 0.400000 0.000000 0.400000 ( 0.396264)
outer21 0.760000 0.000000 0.760000 ( 0.764508)
outer22 0.160000 0.000000 0.160000 ( 0.161982)

--
      vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

Alexandru Popescu wrote:
> While to anybody else this code made things clear, for me it is still
> a little bit confusing:
>
> why outer12 is performing slower than outer22?
>
> According to prev posts, I have understood that the usage of
> block.call requires a conversion to a Proc and this is slower. But in
> above case where is this conversion taking place? (or simply, why is
> it slower?).

>> def outer12(&bl)
>> inner2(&bl)
>> end

The conversion from block to a Proc object happens because of the &bl in the above definition. When outer12 is actually called, the Proc is instantiated and the variable bl is bound to it.

>> def outer22
>> inner2 {yield}
>> end

No & here, so no Proc is created.

···

--
       vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

Thanks Joel... it looks like my initial understanding was wrong. It is
not block.call the one that triggers block to Proc conversion, but in
fact passing blocks as parameters. Is this the correct understanding?

./alex

···

--
.w( the_mindstorm )p.
---
(http://themindstorms.blogspot.com)

On 7/2/06, Joel VanderWerf <vjoel@path.berkeley.edu> wrote:

Alexandru Popescu wrote:
> While to anybody else this code made things clear, for me it is still
> a little bit confusing:
>
> why outer12 is performing slower than outer22?
>
> According to prev posts, I have understood that the usage of
> block.call requires a conversion to a Proc and this is slower. But in
> above case where is this conversion taking place? (or simply, why is
> it slower?).

>> def outer12(&bl)
>> inner2(&bl)
>> end

The conversion from block to a Proc object happens because of the &bl in
the above definition. When outer12 is actually called, the Proc is
instantiated and the variable bl is bound to it.

>> def outer22
>> inner2 {yield}
>> end

No & here, so no Proc is created.

--
       vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

Alexandru Popescu wrote:

Thanks Joel... it looks like my initial understanding was wrong. It is
not block.call the one that triggers block to Proc conversion, but in
fact passing blocks as parameters. Is this the correct understanding?

Right, it happens when the block is passed to a method that expects a "&block" argument (or if the method explicitly calls Proc.new). If you are executing "block.call", then "block" is a variable and it, like any variable, refers to an object.

It's up to the callee, not the caller, to determine how to treat the block: either yield to it or turn it into an object. The former has a small performance advantage (and yield is an elegant notation). The latter allows the Proc object to be saved away somewhere and called even after the method has finished.

···

--
       vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407

I like to compare these situations as 'explicit block argument' vs. 'implicit block argument'. You could also think of it as named vs. anonymous. In any case, as long as the block remains unnamed/implicit there is no need to package it up into an explicit object (i.e. instance of Proc). It is the objectification step that burns up the extra cycles.

Gary Wright

···

On Jul 1, 2006, at 8:05 PM, Joel VanderWerf wrote:

Alexandru Popescu wrote:

Thanks Joel... it looks like my initial understanding was wrong. It is
not block.call the one that triggers block to Proc conversion, but in
fact passing blocks as parameters. Is this the correct understanding?

Right, it happens when the block is passed to a method that expects a "&block" argument (or if the method explicitly calls Proc.new). If you are executing "block.call", then "block" is a variable and it, like any variable, refers to an object.

Hi --

Alexandru Popescu wrote:

Thanks Joel... it looks like my initial understanding was wrong. It is
not block.call the one that triggers block to Proc conversion, but in
fact passing blocks as parameters. Is this the correct understanding?

Right, it happens when the block is passed to a method that expects a "&block" argument (or if the method explicitly calls Proc.new). If you are executing "block.call", then "block" is a variable and it, like any variable, refers to an object.

I like to compare these situations as 'explicit block argument' vs. 'implicit block argument'. You could also think of it as named vs. anonymous. In any case, as long as the block remains unnamed/implicit there is no need to package it up into an explicit object (i.e. instance of Proc). It is the objectification step that burns up the extra cycles.

I see it (subtly) differently. The block itself isn't an argument to
the method; it's a syntactic construct, more analogous to the argument
list than to a particular argument. In that capacity, it's always
explicit; that is, it's written out as part of the method call (by
definition). It's anonymous, but only in the sense that one could say
an argument list is anonymous; there's no ArgumentList class for an
argument list to be a named instance of, and no Block class for blocks
to be named instances of, so anonymity is sort of a red herring.

From that perspective, the objectification is entirely layered on top
of the block; it doesn't change the status of the block per se.

David

···

On Mon, 3 Jul 2006, gwtmp01@mac.com wrote:

On Jul 1, 2006, at 8:05 PM, Joel VanderWerf wrote:

--
David A. Black (dblack@wobblini.net)
Ruby Power and Light, LLC (http://www.rubypowerandlight.com)

See what the readers are saying about "Ruby for Rails"!