Ruby:Proc#call vs yield
以下两种在Ruby中实现的Ruby:Proc#call vs yield,ruby,yield,Ruby,Yield,以下两种在Ruby中实现的trice方法在行为上有什么不同 module WithYield def self.thrice 3.times { yield } # yield to the implicit block argument end end module WithProcCall def self.thrice(&block) # & converts implicit block to an explicit, named Pro
trice
方法在行为上有什么不同
module WithYield
def self.thrice
3.times { yield } # yield to the implicit block argument
end
end
module WithProcCall
def self.thrice(&block) # & converts implicit block to an explicit, named Proc
3.times { block.call } # invoke Proc#call
end
end
WithYield::thrice { puts "Hello world" }
WithProcCall::thrice { puts "Hello world" }
“行为差异”包括错误处理、性能、工具支持等。我认为第一种差异实际上是另一种差异的语法糖分。换句话说,没有行为上的差异 第二种形式允许的是在变量中“保存”块。然后可以在另一个时间点调用该块-回调
嗯。这次我去做了一个快速的基准测试:
require 'benchmark'
class A
def test
10.times do
yield
end
end
end
class B
def test(&block)
10.times do
block.call
end
end
end
Benchmark.bm do |b|
b.report do
a = A.new
10000.times do
a.test{ 1 + 1 }
end
end
b.report do
a = B.new
10000.times do
a.test{ 1 + 1 }
end
end
b.report do
a = A.new
100000.times do
a.test{ 1 + 1 }
end
end
b.report do
a = B.new
100000.times do
a.test{ 1 + 1 }
end
end
end
结果很有趣:
user system total real
0.090000 0.040000 0.130000 ( 0.141529)
0.180000 0.060000 0.240000 ( 0.234289)
0.950000 0.370000 1.320000 ( 1.359902)
1.810000 0.570000 2.380000 ( 2.430991)
这表明使用block.call比使用yield慢近2倍。如果忘记传递块,它们会给出不同的错误消息:
> WithYield::thrice
LocalJumpError: no block given
from (irb):3:in `thrice'
from (irb):3:in `times'
from (irb):3:in `thrice'
> WithProcCall::thrice
NoMethodError: undefined method `call' for nil:NilClass
from (irb):9:in `thrice'
from (irb):9:in `times'
from (irb):9:in `thrice'
但如果您尝试传递“正常”(非块)参数,它们的行为相同:
> WithYield::thrice(42)
ArgumentError: wrong number of arguments (1 for 0)
from (irb):19:in `thrice'
> WithProcCall::thrice(42)
ArgumentError: wrong number of arguments (1 for 0)
from (irb):20:in `thrice'
顺便说一句,只需使用以下工具将此更新到当前日期:
ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-linux]
在英特尔i7(1.5年历史)上
还是慢了2倍。有趣。这里是Ruby 2.x的更新 ruby 2.0.0p247(2013-06-27修订版41674)[x86_64-darwin12.3.0] 我厌倦了手工编写基准测试,所以我创建了一个名为 输出
用户系统总实际值
基准收益率0.930000 0.0000000.930000(0.928682)
板凳呼叫1.650000 0.000000 1.650000(1.652934)
工作台程序0.570000 0.010000 0.580000(0.578605)
我认为最令人惊讶的是,
bench\u收益率
比bench\u proc
慢。我希望我能多了解一点为什么会发生这种情况。其他答案非常全面,涵盖了功能上的差异。我很好奇哪种方法对于那些可以选择接受块的方法来说性能最好,所以我写了一些基准测试(即将开始)。我比较了三种方法:
- &块内方法签名
- 使用
&Proc.new
- 在另一个块中包装
产量
require "benchmark"
def always_yield
yield
end
def sometimes_block(flag, &block)
if flag && block
always_yield &block
end
end
def sometimes_proc_new(flag)
if flag && block_given?
always_yield &Proc.new
end
end
def sometimes_yield(flag)
if flag && block_given?
always_yield { yield }
end
end
a = b = c = 0
n = 1_000_000
Benchmark.bmbm do |x|
x.report("no &block") do
n.times do
sometimes_block(false) { "won't get used" }
end
end
x.report("no Proc.new") do
n.times do
sometimes_proc_new(false) { "won't get used" }
end
end
x.report("no yield") do
n.times do
sometimes_yield(false) { "won't get used" }
end
end
x.report("&block") do
n.times do
sometimes_block(true) { a += 1 }
end
end
x.report("Proc.new") do
n.times do
sometimes_proc_new(true) { b += 1 }
end
end
x.report("yield") do
n.times do
sometimes_yield(true) { c += 1 }
end
end
end
Ruby 2.0.0p247和1.9.3p392的性能相似。以下是1.9.3的结果:
user system total real
no &block 0.580000 0.030000 0.610000 ( 0.609523)
no Proc.new 0.080000 0.000000 0.080000 ( 0.076817)
no yield 0.070000 0.000000 0.070000 ( 0.077191)
&block 0.660000 0.030000 0.690000 ( 0.689446)
Proc.new 0.820000 0.030000 0.850000 ( 0.849887)
yield 0.250000 0.000000 0.250000 ( 0.249116)
当不总是使用显式&block
参数时,添加该参数确实会降低方法的速度。如果该块是可选的,则不要将其添加到方法签名中。而且,对于传递块,在另一个块中包装yield
是最快的
也就是说,这些是一百万次迭代的结果,所以不要太担心。如果有一种方法以百万分之一秒为代价使代码更清晰,那么还是使用它吧。我发现结果会有所不同,这取决于您是否强制Ruby构造块(例如,一个预先存在的过程) 给出了结果:
Ruby 2.3.1 at 2016-11-15 23:55:38 +1300
Warming up --------------------------------------
block.call 266.502k i/100ms
yield with block 269.487k i/100ms
yield 262.597k i/100ms
Calculating -------------------------------------
block.call 8.271M (± 5.4%) i/s - 41.308M in 5.009898s
yield with block 11.754M (± 4.8%) i/s - 58.748M in 5.011017s
yield 16.206M (± 5.6%) i/s - 80.880M in 5.008679s
Comparison:
yield: 16206091.2 i/s
yield with block: 11753521.0 i/s - 1.38x slower
block.call: 8271283.9 i/s - 1.96x slower
如果将
do\u-call(&existing\u-block)
更改为do\u-call{}
,您会发现在这两种情况下,它的速度都要慢5倍左右。我认为这样做的原因应该是显而易见的(因为Ruby被迫为每次调用构造一个Proc)。如果这是真的,我认为Ruby会更加一致(即如果yield
只是Proc\call
的语法糖),但我不认为这是真的。e、 g.存在不同的错误处理行为(见下面我的答案)。我还看到有人建议(例如),yield
效率更高,因为它不必先创建一个Proc
对象,然后调用其call
方法,在MRI 1.8.6p114上。在JRuby(1.3.0,JVM1.6.0_16服务器虚拟机)上,差异更为显著:Proc\35; call
的速度大约是yield
的8倍。也就是说,JRuby上的yield
速度是MRI上的yield
速度的两倍。我在MRI 1.8.7p174 x86_64-linux上做了我的工作。您还缺少第三种情况:def测试(&block);10.次数和块数;结束
,测试结果应与收益率情况相同<代码>块。调用比yield
慢约1.7倍。我相信这是因为在bench\u proc
中,一元运算符实际上是将proc转换为次
调用的块,跳过bench\u yield
和bench\u调用
中的次
的块创建开销。这是一种奇怪的特殊情况使用,看起来在大多数情况下,yield
的速度更快。有关proc到块赋值的更多信息:(部分:一元数&)Integer#次
调用yield
(c版本,rb_yield,采用表示块的值)。这就是为什么bench_proc这么快。宝石怎么了?不同类型的ruby闭包之间的行为差异旁注:def-three(&block)
更加自我记录,特别是与隐藏在大型方法中的yield
相比。关于cldwalker提供的链接的说明……这是错误的。可以将多个块(即闭包)传递给一个方法。(对于提到“编译”Ruby的人来说,也很难认真对待。)传递多个块时,你会得到同样方便的语法糖吗?不,你能做到吗?是的,很容易。
user system total real
no &block 0.580000 0.030000 0.610000 ( 0.609523)
no Proc.new 0.080000 0.000000 0.080000 ( 0.076817)
no yield 0.070000 0.000000 0.070000 ( 0.077191)
&block 0.660000 0.030000 0.690000 ( 0.689446)
Proc.new 0.820000 0.030000 0.850000 ( 0.849887)
yield 0.250000 0.000000 0.250000 ( 0.249116)
require 'benchmark/ips'
puts "Ruby #{RUBY_VERSION} at #{Time.now}"
puts
firstname = 'soundarapandian'
middlename = 'rathinasamy'
lastname = 'arumugam'
def do_call(&block)
block.call
end
def do_yield(&block)
yield
end
def do_yield_without_block
yield
end
existing_block = proc{}
Benchmark.ips do |x|
x.report("block.call") do |i|
buffer = String.new
while (i -= 1) > 0
do_call(&existing_block)
end
end
x.report("yield with block") do |i|
buffer = String.new
while (i -= 1) > 0
do_yield(&existing_block)
end
end
x.report("yield") do |i|
buffer = String.new
while (i -= 1) > 0
do_yield_without_block(&existing_block)
end
end
x.compare!
end
Ruby 2.3.1 at 2016-11-15 23:55:38 +1300
Warming up --------------------------------------
block.call 266.502k i/100ms
yield with block 269.487k i/100ms
yield 262.597k i/100ms
Calculating -------------------------------------
block.call 8.271M (± 5.4%) i/s - 41.308M in 5.009898s
yield with block 11.754M (± 4.8%) i/s - 58.748M in 5.011017s
yield 16.206M (± 5.6%) i/s - 80.880M in 5.008679s
Comparison:
yield: 16206091.2 i/s
yield with block: 11753521.0 i/s - 1.38x slower
block.call: 8271283.9 i/s - 1.96x slower