Feature #14045
closedLazy Proc allocation for block parameters
Description
Background¶
If we need to pass given block, we need to capture by block parameter as a Proc object and pass it parameter as a block argument. Like that:
def block_yield
yield
end
def block_pass &b
# do something
block_yield(&b)
end
There are no way to pass given blocks to other methods without using block parameters.
One problem of this technique is performance. Proc
creation is one of heavyweight operation because we need to store all of local variables (represented by Env objects in MRI internal) to heap. If block parameter is declared as one of method parameter, we need to make a new Proc
object for the block parameter.
Proposal: Lazy Proc allocation for¶
To avoid this overhead, I propose lazy Proc creation for block parameters.
Ideas:
- At the beginning of method, a block parameter is
nil
- If block parameter is accessed, then create a
Proc
object by given block. - If we pass the block parameter to other methods like
block_yield(&b)
then don't make aProc
, but pass given block information.
We don't optimize b.call
type block invocations. If we call block with b.call
, then create Proc
object.We need to hack more because Proc#call
is different from yield
statement (especially they can change $SAFE
).
Evaluation¶
def iter_yield
yield
end
def iter_pass &b
iter_yield(&b)
end
def iter_yield_bp &b
yield
end
def iter_call &b
b.call
end
N = 10_000_000 # 10M
require 'benchmark'
Benchmark.bmbm(10){|x|
x.report("yield"){
N.times{
iter_yield{}
}
}
x.report("yield_bp"){
N.times{
iter_yield_bp{}
}
}
x.report("yield_pass"){
N.times{
iter_pass{}
}
}
x.report("send_pass"){
N.times{
send(:iter_pass){}
}
}
x.report("call"){
N.times{
iter_call{}
}
}
}
__END__
ruby 2.5.0dev (2017-10-24 trunk 60392) [x86_64-linux]
user system total real
yield 0.634891 0.000000 0.634891 ( 0.634518)
yield_bp 2.770929 0.000008 2.770937 ( 2.769743)
yield_pass 3.047114 0.000000 3.047114 ( 3.046895)
send_pass 3.322597 0.000002 3.322599 ( 3.323657)
call 3.144668 0.000000 3.144668 ( 3.143812)
modified
user system total real
yield 0.582620 0.000000 0.582620 ( 0.582526)
yield_bp 0.731068 0.000000 0.731068 ( 0.730315)
yield_pass 0.926866 0.000000 0.926866 ( 0.926902)
send_pass 1.110110 0.000000 1.110110 ( 1.109579)
call 2.891364 0.000000 2.891364 ( 2.890716)
Related work¶
To delegate the given block to other methods, Single &
block parameter had been proposed (https://bugs.ruby-lang.org/issues/3447#note-18) (using like: def foo(&); bar(&); end
). This idea is straightforward to represent block passing
. Also we don't need to name a block parameter.
The advantage of this ticket proposal is we don't change any syntax. We can write compatible code for past versions.
Thanks,
Koichi