IT Share you

Ruby에서 동적 메서드 호출

shareyou 2020. 11. 8. 11:33
반응형

Ruby에서 동적 메서드 호출


내가 아는 한 Ruby에서 동적으로 메서드를 호출하는 세 가지 방법이 있습니다.

방법 1 :

s = SomeObject.new
method = s.method(:dynamic_method)
method.call

방법 2 :

s = SomeObject.new
s.send(:dynamic_method)

방법 3 :

s = SomeObject.new
eval "s.dynamic_method"

이를 벤치마킹하여 방법 1이 가장 빠르고 방법 2가 더 느리며 방법 3이 가장 느리다는 것을 확인했습니다.

나는 또한 것을 발견 .call하고 .send있는 동안 모두는 개인 메소드를 호출 할 수 eval하지 않습니다.

그래서 내 질문은 : .send또는 사용할 이유가 eval있습니까? 항상 가장 빠른 방법을 사용하지 않는 이유는 무엇입니까? 이러한 동적 메서드 호출 방법의 다른 차이점은 무엇입니까?


사용할 이유가 send있습니까?

call메소드 객체가 send필요하지만 다음을 수행하지 않습니다.

class Foo
  def method_missing(name)
    "#{name} called"
  end
end

Foo.new.send(:bar)         #=> "bar called"
Foo.new.method(:bar).call  #=> undefined method `bar' for class `Foo' (NameError)

사용할 이유가 eval있습니까?

eval 메서드를 호출하기위한 것이 아닙니다.


벤치 마크와 관련하여 + send보다 빠른 것 같습니다 .methodcall

require 'benchmark'

class Foo
  def bar; end
end

Benchmark.bm(4) do |b|
  b.report("send") { 1_000_000.times { Foo.new.send(:bar) } }
  b.report("call") { 1_000_000.times { Foo.new.method(:bar).call } }
end

결과:

           user     system      total        real
send   0.210000   0.000000   0.210000 (  0.215181)
call   0.740000   0.000000   0.740000 (  0.739262)

다음과 같이 생각하십시오.

방법 1 (method.call) : 단일 런타임

프로그램에서 Ruby를 한 번만 실행하면 전체 시스템을 제어하고 "method.call"접근 방식을 통해 "메서드에 대한 포인터"를 유지할 수 있습니다. 당신이하는 일은 당신이 원할 때마다 실행할 수있는 "라이브 코드"에 대한 핸들을 잡는 것뿐입니다. 이것은 기본적으로 객체 내에서 직접 메서드를 호출하는 것만 큼 빠릅니다 (그러나 object.send를 사용하는 것만 큼 빠르지는 않습니다-다른 답변의 벤치 마크 참조).

방법 2 (object.send) : 데이터베이스에 메서드 이름 유지

그러나 호출하려는 메소드의 이름을 데이터베이스에 저장하고 향후 애플리케이션에서 데이터베이스에서 검색하여 해당 메소드 이름을 호출하려면 어떻게해야할까요? 그런 다음 두 번째 접근 방식을 사용하면 루비가 두 번째 "s.send (: dynamic_method)"접근 방식을 사용하여 임의의 메서드 이름을 호출하게됩니다.

방법 3 (평가) :자가 수정 방법 코드

메서드를 새로운 코드로 실행하는 방식으로 데이터베이스에 코드를 작성 / 수정 / 지속하려면 어떻게해야합니까? 데이터베이스에 기록 된 코드를 주기적으로 수정하고 매번 새 코드로 실행되도록 할 수 있습니다. 이 (매우 드문 경우) 세 번째 접근 방식을 사용하여 메서드 코드를 문자열로 작성하고 나중에 다시로드하고 전체를 실행할 수 있습니다.

가치가 있기 때문에 일반적으로 루비 세계에서는 매우 난해하고 드문 경우를 제외하고 Eval (방법 3)을 사용하는 것이 나쁜 형태로 간주됩니다. 따라서 발생하는 거의 모든 문제에 대해 방법 1과 2를 고수해야합니다.


가능한 모든 메서드 호출은 다음과 같습니다.

require 'benchmark/ips'

class FooBar
  def name; end
end

el = FooBar.new

Benchmark.ips do |x|
  x.report('plain') { el.name }
  x.report('eval') { eval('el.name') }
  x.report('method call') { el.method(:name).call }
  x.report('send sym') { el.send(:name) }
  x.report('send str') { el.send('name') }
  x.compare!
end

결과는 다음과 같습니다.

Warming up --------------------------------------
               plain   236.448k i/100ms
                eval    20.743k i/100ms
         method call   131.408k i/100ms
            send sym   205.491k i/100ms
            send str   168.137k i/100ms
Calculating -------------------------------------
               plain      9.150M (± 6.5%) i/s -     45.634M in   5.009566s
                eval    232.303k (± 5.4%) i/s -      1.162M in   5.015430s
         method call      2.602M (± 4.5%) i/s -     13.009M in   5.010535s
            send sym      6.729M (± 8.6%) i/s -     33.495M in   5.016481s
            send str      4.027M (± 5.7%) i/s -     20.176M in   5.027409s

Comparison:
               plain:  9149514.0 i/s
            send sym:  6729490.1 i/s - 1.36x  slower
            send str:  4026672.4 i/s - 2.27x  slower
         method call:  2601777.5 i/s - 3.52x  slower
                eval:   232302.6 i/s - 39.39x  slower

일반 호출이 가장 빠르며 추가 할당, 기호 조회, 메소드 조회 및 평가 만 필요합니다.

에 관해서는 send기호를 통해, 더 빨리 심볼에 대한 훨씬 더 easer 할당 할 메모리와 같은 문자열을 통해보다. 정의되면 메모리에 장기간 저장되며 재 할당이 없습니다.

같은 이유는 method(:name)(1) Proc객체에 대한 메모리를 할당해야 합니다. (2) 우리는 클래스에서 메서드를 호출하여 시간이 걸리는 추가 메서드 조회를 유도합니다.

eval is runs interpreter so it's the heaviest.


I updated the benchmark from @Stefan to check if there are some speed improvements when saving reference to method. But again – send is much faster than call

require 'benchmark'

class Foo
  def bar; end
end

foo = Foo.new
foo_bar = foo.method(:bar)

Benchmark.bm(4) do |b|
  b.report("send") { 1_000_000.times { foo.send(:bar) } }
  b.report("call") { 1_000_000.times { foo_bar.call } }
end

These are the results:

           user     system      total        real
send   0.080000   0.000000   0.080000 (  0.088685)
call   0.110000   0.000000   0.110000 (  0.108249)

So send seems to be the one to take.


The whole point of send and eval is that you can change the command dynamically. If the method you want to execute is fixed, then you can hard-wire that method without using send or eval.

receiver.fixed_method(argument)

But when you want to invoke a method that varies or you do not know in advance, then you cannot write that directly. Hence the use of send or eval.

receiver.send(method_that_changes_dynamically, argument)
eval "#{code_to_evaluate_that_changes_more_dramatically}"

Additional use of send is that, as you noticed, you can call a method with explicit receiver using send.

참고URL : https://stackoverflow.com/questions/17454992/dynamic-method-calling-in-ruby

반응형