루비 : Proc # call vs yield
루비의 다음 두 구현 방식의 동작 차이는 무엇입니까 thrice
?
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 배 느리다 는 것을 보여줍니다 .
다음은 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
. 왜 이런 일이 일어나는지 조금 더 이해했으면 좋겠습니다.
블록을 전달하는 것을 잊으면 다른 오류 메시지가 표시됩니다.
> 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의 Closures 는 기능적 차이점을 광범위하게 다룹니다. 블록 을 선택적으로 허용하는 방법에 대해 어떤 방법이 가장 잘 수행 될지 궁금해서 몇 가지 벤치 마크를 작성했습니다 ( 이 Paul Mucur 게시물 참조 ). 세 가지 방법을 비교했습니다.
- & block in method signature
- 사용
&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
때 다른 블록으로 감싸는 것이 가장 빠릅니다.
즉, 이것은 백만 번의 반복 결과이므로 너무 걱정하지 마십시오. 한 가지 방법이 백만 분의 1 초의 비용으로 코드를 더 명확하게 만들어 준다면 어쨌든 그것을 사용하십시오.
루비가 블록을 생성하도록 강요하는지 여부 (예 : 기존 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
If you change do_call(&existing_block)
to do_call{}
you'll find it's about 5x slower in both cases. I think the reason for this should be obvious (because Ruby is forced to construct a Proc for each invocation).
BTW, just to update this to current day using:
ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-linux]
On Intel i7 (1.5 years oldish).
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)
Still 2x slower. Interesting.
참고URL : https://stackoverflow.com/questions/1410160/ruby-proccall-vs-yield
'programing' 카테고리의 다른 글
Spring MVC에서 인터셉터와 필터의 차이점 (0) | 2020.10.22 |
---|---|
웹 개발에서 프런트 엔드, 백엔드 및 미들웨어의 차이점 (0) | 2020.10.22 |
Phonegap + jQuery Mobile, 실제 샘플 또는 튜토리얼 (0) | 2020.10.22 |
package.json에 node_modules 경로 지정 (0) | 2020.10.22 |
오류 : Microsoft Visual C ++ 10.0이 필요합니다 (vcvarsall.bat를 찾을 수 없음). (0) | 2020.10.21 |