ruby metaprogramming dynamic-method

Llamada a un método dinámico en Ruby



metaprogramming dynamic-method (5)

Por lo que sé, hay tres formas de llamar dinámicamente un método en Ruby:

Método 1:

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

Método 2:

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

Método 3:

s = SomeObject.new eval "s.dynamic_method"

Al compararlos, he establecido que el Método 1 es, con mucho, el más rápido, el Método 2 es más lento y el Método 3 es con mucho el más lento.

También encontré que .call y .send permiten llamar a métodos privados, mientras que eval no.

Entonces mi pregunta es: ¿hay alguna razón para usar .send o eval ? ¿Por qué no siempre usarías el método más rápido? ¿Qué otras diferencias tienen estos métodos para llamar a los métodos dinámicos?


¿Hay alguna razón para usar send ?

call necesita un objeto de método, el send no:

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)

¿Hay alguna razón para usar alguna vez eval ?

eval evalúa expresiones arbitrarias, no es solo para llamar a un método.

En cuanto a los puntos de referencia, el send parece ser más rápido que el method + call :

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

Resultado:

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


Actualicé el punto de referencia de @Stefan para verificar si hay algunas mejoras de velocidad al guardar la referencia al método. Pero de nuevo, send es mucho más rápido que 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

Estos son los resultados:

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

Así que send parece ser el que tomar.


Aquí están todas las llamadas a métodos posibles:

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

Y los resultados son:

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

Se espera que la llamada simple sea la más rápida, sin asignaciones adicionales, búsquedas de símbolos, solo búsqueda y evaluación del método.

En cuanto a send través de un símbolo, es más rápido que a través de una cadena ya que es mucho más fácil asignar memoria para el símbolo. Una vez que está definido, se almacena a largo plazo en la memoria y no hay reasignaciones.

La misma razón se puede decir sobre el method(:name) (1) es necesario asignar memoria para el objeto Proc (2) estamos llamando al método en la clase que conduce a la búsqueda de métodos adicionales, que también lleva tiempo.

eval se ejecuta intérprete por lo que es el más pesado.


El objetivo de send y eval es que puedes cambiar el comando de forma dinámica. Si el método que desea ejecutar es fijo, entonces puede cablear ese método sin utilizar send o eval .

receiver.fixed_method(argument)

Pero cuando desea invocar un método que varía o no sabe de antemano, no puede escribirlo directamente. De ahí el uso de send o eval .

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

El uso adicional de send es que, como habrás notado, puedes llamar a un método con un receptor explícito usando send .


Piénsalo de esta manera:

Método 1 (method.call): Single run-time

Si ejecuta Ruby una vez en su programa directamente, usted controla todo el sistema y puede mantener un "puntero a su método" a través del método "method.call". Todo lo que está haciendo es mantener un identificador de "código en vivo" que puede ejecutar cuando lo desee. Esto es básicamente tan rápido como llamar al método directamente desde el objeto (pero no es tan rápido como usar object.send - ver benchmarks en otras respuestas).

Método 2 (object.send): persistir el nombre del método en la base de datos

Pero, ¿qué ocurre si desea almacenar el nombre del método que desea llamar en una base de datos y, en una aplicación futura, desea llamar al nombre de ese método buscándolo en la base de datos? Luego usaría el segundo enfoque, que hace que Ruby llame a un nombre de método arbitrario usando su segundo enfoque "s.send (: dynamic_method)".

Método 3 (eval): código de método de auto modificación

¿Qué sucede si quiere escribir / modificar / persistir el código en una base de datos de una manera que ejecute el método como un código nuevo? Puede modificar periódicamente el código escrito en la base de datos y querer que se ejecute como código nuevo cada vez. En este (caso muy inusual), querrá utilizar su tercer enfoque, que le permite escribir su código de método como una cadena, cargarlo de nuevo en una fecha posterior y ejecutarlo en su totalidad.

Por lo que vale, generalmente se considera en el mundo de Ruby como una forma mala de usar Eval (método 3) excepto en casos muy, muy esotéricos y raros. Por lo tanto, debe seguir con los métodos 1 y 2 para casi todos los problemas que encuentre.