2010-06-17 12 views
10

Tengo dos scripts que usan Mechanize para buscar una página de índice de Google. Supuse que EventMachine sería más rápido que un hilo de Ruby, pero no lo es.¿Por qué el aplazamiento de EventMachine es más lento que un subproceso de Ruby?

costos de código EventMachine: "0.24s user 0.08s system 2% cpu 12.682 total"

Rubí costos de código Tema: "0.22s user 0.08s system 5% cpu 5.167 total "

estoy usando EventMachine en el camino equivocado?

EventMachine:

require 'rubygems' 
require 'mechanize' 
require 'eventmachine' 

trap("INT") {EM.stop} 

EM.run do 
    num = 0 
    operation = proc { 
    agent = Mechanize.new 
    sleep 1 
    agent.get("http://google.com").body.to_s.size 
    } 
    callback = proc { |result| 
    sleep 1 
    puts result 
    num+=1 
    EM.stop if num == 9 
    } 

    10.times do 
    EventMachine.defer operation, callback 
    end 
end 

Rubí Tema:

require 'rubygems' 
require 'mechanize' 


threads = [] 
10.times do 
    threads << Thread.new do 
    agent = Mechanize.new 
    sleep 1 
    puts agent.get("http://google.com").body.to_s.size 
    sleep 1 
    end 
end 


threads.each do |aThread| 
    aThread.join 
end 
+0

¿Qué versión e implementación de ruby ​​está ejecutando? Para implementaciones con un GIL (bloqueo de intérprete global), es posible que los subprocesos verdes no se ejecuten completamente al mismo tiempo. Puede intentar ejecutar el ejemplo en jRuby o Rubinius para confirmar su comportamiento observado –

Respuesta

9

Sí, se está usando mal. EventMachine funciona realizando llamadas I/O asincrónicas que regresan inmediatamente y notifican al "reactor" (el bucle de evento iniciado por EM.run) cuando se completan. Tienes dos llamadas de bloqueo que frustran el propósito del sistema, dormir y Mechanize.get. Debe usar bibliotecas especiales asíncronas/no bloqueantes para obtener cualquier valor de EventMachine.

+2

Tiene razón en que el ejemplo que planteó se puede reescribir con una biblioteca http asincrónica, pero el punto del método #defer es específicamente para que pueda engendrar un nuevo hilo que realiza una operación de bloqueo sin afectar el ciclo de ejecución del reactor. Entonces teóricamente, su ejemplo no está bloqueando el ciclo de ejecución. Mi conjetura con la diferencia de tiempo es cómo están programados los hilos. –

+0

En general, EventMachine funciona exactamente como usted dijo, pero su respuesta no se aplica a su uso de la llamada 'defer'. –

2

EventMachine "diferir" en realidad genera subprocesos de rubíes de una subprocesos se las arregla para gestionar su solicitud. Sí, EventMachine está diseñado para operaciones IO sin bloqueo, pero el comando defer es una excepción, está diseñado para permitirle realizar operaciones de larga duración sin bloquear el reactor.

Por lo tanto, va a ser un poco más lento que los subprocesos desnudos, porque en realidad solo está iniciando subprocesos con la sobrecarga del administrador de subprocesos de EventMachine.

Puede leer más sobre Defer aquí: http://eventmachine.rubyforge.org/EventMachine.html#M000486

Dicho esto, ir a buscar páginas es un gran uso de EventMachine, pero como otros críticos han dicho, es necesario utilizar una biblioteca IO no bloqueante, y luego usar next_tick o similar para comenzar sus tareas, en lugar de diferir, lo que rompe su tarea fuera del circuito del reactor.

+0

El enlace FYI está roto. –

24

A todas las respuestas en este hilo les falta un punto clave: sus devoluciones de llamada se están ejecutando dentro del hilo del reactor en lugar de en un hilo diferido por separado. Ejecutar solicitudes de Mechanize en una llamada a defer es la forma correcta de evitar el bloqueo del ciclo, pero debe tener cuidado de que su devolución de llamada tampoco bloquee el ciclo.

Cuando ejecuta EM.defer operation, callback, la operación se ejecuta dentro de un subproceso generado por Ruby, que hace el trabajo, y luego se emite la devolución de llamada dentro del bucle principal. Por lo tanto, el sleep 1 en operation se ejecuta en paralelo, pero la devolución de llamada ejecuta en serie. Esto explica la diferencia de casi 9 segundos en el tiempo de ejecución.

Aquí hay una versión simplificada del código que está ejecutando.

EM.run { 
    times = 0 

    work = proc { sleep 1 } 

    callback = proc { 
    sleep 1 
    EM.stop if (times += 1) >= 10 
    } 

    10.times { EM.defer work, callback } 
} 

Esto toma alrededor de 12 segundos, que es 1 segundo para los duerme paralelas, 10 segundos para los serie Camas, y 1 segundo para la sobrecarga.

para ejecutar el código de devolución de llamada en paralelo, hay que generar nuevos temas para que el uso de una devolución de llamada de proxy que utiliza EM.defer así:

EM.run { 
    times = 0 

    work = proc { sleep 1 } 

    callback = proc { 
    sleep 1 
    EM.stop if (times += 1) >= 10 
    } 

    proxy_callback = proc { EM.defer callback } 

    10.times { EM.defer work, proxy_callback } 
} 

Sin embargo, es posible que tenga problemas con esto si su devolución de llamada es entonces se supone que debe ejecutar código dentro del bucle de evento, porque se ejecuta dentro de un subproceso diferido por separado. Si esto sucede, mueva el código del problema a la devolución de llamada del proxy_callback proc.

EM.run { 
    times = 0 

    work = proc { sleep 1 } 

    callback = proc { 
    sleep 1 
    EM.stop_event_loop if (times += 1) >= 5 
    } 

    proxy_callback = proc { EM.defer callback, proc { "do_eventmachine_stuff" } } 

    10.times { EM.defer work, proxy_callback } 
} 

Esta versión corrió en aproximadamente 3 segundos, lo que explica por 1 segundo de dormir para el funcionamiento en paralelo, 1 segundo de dormir para devolución de llamada en paralelo y 1 segundo para la sobrecarga.

+0

Gracias Ben! Tomé el ejemplo de tu proxy un poco más y creé una función de desbloqueo. Puedes ver mi implementación aquí: http://goo.gl/8kbc6y –

Cuestiones relacionadas