在thrice
方法的Ruby中,以下两个实现之间的行为差异是什么?
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" }
通过“行为差异”,我包括错误处理,性能,工具支持等。
答案 0 :(得分:50)
我认为第一个实际上是另一个的语法糖。换句话说,没有行为差异。
第二种形式允许的是将块“保存”在变量中。然后可以在其他某个时间点调用该块 - 回调。
确定。这次我去做了一个快速的基准测试:
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倍。
答案 1 :(得分:9)
这是Ruby 2.x的更新
ruby 2.0.0p247(2013-06-27修订版41674)[x86_64-darwin12.3.0]
我厌倦了手动编写基准测试,因此我创建了一个名为benchable
的小跑步模块require 'benchable' # https://gist.github.com/naomik/6012505
class YieldCallProc
include Benchable
def initialize
@count = 10000000
end
def bench_yield
@count.times { yield }
end
def bench_call &block
@count.times { block.call }
end
def bench_proc &block
@count.times &block
end
end
YieldCallProc.new.benchmark
输出
user system total real
bench_yield 0.930000 0.000000 0.930000 ( 0.928682)
bench_call 1.650000 0.000000 1.650000 ( 1.652934)
bench_proc 0.570000 0.010000 0.580000 ( 0.578605)
我认为最令人惊讶的是bench_yield
比bench_proc
慢。我希望我能更好地理解为什么会发生这种情况。
答案 2 :(得分:6)
如果您忘记传递阻止,他们会给出不同的错误消息:
> 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'
答案 3 :(得分:5)
其他答案非常详尽,Closures in Ruby广泛涵盖了功能差异。我很好奇哪种方法对于可选接受一个块的方法最有效,所以我写了一些基准(关闭this Paul Mucur post)。我比较了三种方法:
&Proc.new
yield
包装在另一个块中以下是代码:
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
包装在另一个块中是最快的。
也就是说,这些是一百万次迭代的结果,所以不要太担心它。如果一种方法以百万分之一秒的代价使你的代码更清晰,那么无论如何都要使用它。
答案 4 :(得分:2)
我发现结果是不同的,这取决于你是否强制Ruby构造块(例如,预先存在的proc)。
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
如果您将do_call(&existing_block)
更改为do_call{}
,您会发现在两种情况下都会慢大约5倍。我认为其原因应该是显而易见的(因为Ruby被迫为每次调用构建一个Proc)。
答案 5 :(得分:0)
BTW,只是使用以下内容将此更新为当天:
ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-linux]
在Intel i7上(1.5岁)。
user system total real
0.010000 0.000000 0.010000 ( 0.015555)
0.030000 0.000000 0.030000 ( 0.024416)
0.120000 0.000000 0.120000 ( 0.121450)
0.240000 0.000000 0.240000 ( 0.239760)
仍然慢2倍。有趣。