在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" }
Run Code Online (Sandbox Code Playgroud)
通过"行为差异",我包括错误处理,性能,工具支持等.
jpa*_*zek 51
我认为第一个实际上是另一个的语法糖.换句话说,没有行为差异.
第二种形式允许的是将块"保存"在变量中.然后可以在其他某个时间点调用该块 - 回调.
好.这次我去做了一个快速的基准测试:
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
Run Code Online (Sandbox Code Playgroud)
结果很有趣:
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)
Run Code Online (Sandbox Code Playgroud)
这表明使用block.call几乎比使用yield慢2 倍.
这是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
Run Code Online (Sandbox Code Playgroud)
产量
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)
Run Code Online (Sandbox Code Playgroud)
我认为这里最令人惊讶的bench_yield是比它慢bench_proc.我希望我能更好地理解为什么会这样.
如果您忘记传递块,它们会给出不同的错误消息:
> 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'
Run Code Online (Sandbox Code Playgroud)
但如果您尝试传递"正常"(非阻塞)参数,它们的行为相同:
> 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'
Run Code Online (Sandbox Code Playgroud)
其他答案非常详尽,Ruby中的Closures广泛涵盖了功能差异.我很好奇哪种方法对于可选择接受块的方法最有效,所以我写了一些基准(关于这个Paul Mucur的帖子).我比较了三种方法:
&Proc.newyield在另一个街区这是代码:
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
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
在&block不总是使用时添加显式参数确实会减慢方法的速度.如果块是可选的,请不要将其添加到方法签名中.并且,为了传递块,yield在另一个块中包装是最快的.
也就是说,这些是一百万次迭代的结果,所以不要太担心它.如果一种方法以百万分之一秒的代价使您的代码更清晰,那么无论如何都要使用它.