2010-06-17 13 views
10

Ich habe zwei Skripte, die Mechanize verwenden, um eine Google-Indexseite abzurufen. Ich nahm an, dass EventMachine schneller ist als ein Ruby-Thread, aber das ist es nicht.Warum verzögert sich EventMachines langsamer als ein Ruby-Thread?

eventmachine Code Kosten: "0.24s user 0.08s system 2% cpu 12.682 total"

Ruby-Themen-Code Kosten: "0.22s user 0.08s system 5% cpu 5.167 total "

Bin ich mit eventmachine in die falsche Richtung?

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 

Rubin Thema:

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

Welche Version und Implementierung von Ruby verwenden Sie? Bei Implementierungen mit einer GIL (globale Interpretersperre) werden die grünen Threads möglicherweise nicht vollständig gleichzeitig ausgeführt. Vielleicht möchten Sie versuchen, das Beispiel in jRuby oder Rubinius auszuführen, um Ihr beobachtetes Verhalten zu bestätigen. –

Antwort

9

Yep, Sie verwenden es falsch. EventMachine arbeitet mit asynchronen IO-Aufrufen, die sofort zurückkehren und den "Reaktor" (die von EM.run gestartete Ereignisschleife) benachrichtigen, wenn sie abgeschlossen sind. Sie haben zwei blockierende Aufrufe, die den Zweck des Systems, den Schlaf und Mechanize.get besiegen. Sie müssen spezielle asynchrone/nicht-blockierende Bibliotheken verwenden, um einen beliebigen Wert von EventMachine abzuleiten.

+2

Sie haben Recht, dass das von ihm gestellte Beispiel mit einer asynchronen http-Bibliothek umgeschrieben werden kann, aber der Punkt der Methode #defer ist speziell so, dass Sie spawnen können ein neuer Thread, der eine Blockierung durchführt, ohne die Reaktorlaufschleife zu beeinflussen. Theoretisch blockiert sein Beispiel also die Laufschleife nicht. Meine Schätzung mit dem Zeitunterschied ist, wie die Threads geplant sind. –

+0

Im Allgemeinen funktioniert EventMachine genau so, wie Sie es gesagt haben, aber Ihre Antwort gilt nicht für die Verwendung des "Defer" -Aufrufs. –

2

eventmachine „verschieben“ laicht eigentlich Ruby-Threads von einem Thread es verwaltet Ihre Anfrage zu bearbeiten. Ja, EventMachine ist für nicht blockierende IO-Operationen ausgelegt, aber der Befehl "defer" ist eine Ausnahme - er ermöglicht es Ihnen, lang andauernde Operationen durchzuführen, ohne den Reaktor zu blockieren.

Also, es wird etwas langsamer als nackte Threads, weil es wirklich nur Threads mit Overhead des Threadpool-Managers von EventMachine startet.

Sie können hier mehr über defer lesen: http://eventmachine.rubyforge.org/EventMachine.html#M000486

Das heißt, Seiten zu holen ist ein großer Einsatz von eventmachine, aber wie andere Plakate gesagt haben, müssen Sie eine nicht-blockierende IO-Bibliothek verwenden, und verwenden Sie dann next_tick oder ähnlich, um Ihre Aufgaben zu starten, anstatt sie zu verschieben, wodurch Ihre Aufgabe aus der Reaktorschleife herausfällt.

+0

FYI Verbindung ist kaputt. –

24

Allen Antworten in diesem Thread fehlt ein wichtiger Punkt: Ihre Rückrufe werden innerhalb des Reaktorthreads statt in einem separaten verzögerten Thread ausgeführt. Das Ausführen von Mechanize-Anfragen in einem defer-Aufruf ist der richtige Weg, um die Schleife nicht zu blockieren, aber Sie müssen darauf achten, dass Ihr Callback nicht auch die Schleife blockiert.

Wenn Sie EM.defer operation, callback ausführen, wird die Operation in einem Ruby-erzeugten Thread ausgeführt, der die Arbeit ausführt, und dann wird der Rückruf innerhalb der Hauptschleife ausgelöst. Daher läuft die sleep 1 in operation parallel, aber der Rückruf läuft seriell. Dies erklärt den Unterschied von fast 9 Sekunden in der Laufzeit.

Hier ist eine vereinfachte Version des Codes, den Sie ausführen.

EM.run { 
    times = 0 

    work = proc { sleep 1 } 

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

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

Dieser Vorgang dauert etwa 12 Sekunden, 1 Sekunde, die für die parallelen schläft, ist, 10 Sekunden für die serielle schläft und 1 Sekunde für den Overhead.

Um den Callback-Code parallel zu laufen, haben Sie es neue Threads zu erstellen, einen Proxy-Rückruf verwenden, die EM.defer wie so verwendet:

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 } 
} 

jedoch Sie in Probleme mit diesem laufen kann, wenn Ihr Rückruf dann sollte Code innerhalb der Ereignisschleife ausgeführt werden, da er in einem separaten, verzögerten Thread ausgeführt wird. Wenn dies geschieht, verschieben Sie den Problemcode in den Rückruf des 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 } 
} 

Diese Version lief in ca. 3 Sekunden, die parallel zum Betrieb des Schlafens für 1 Sekunde ausmachen, 1 Sekunde für Überkopf in parallel und 1 Sekunde für den Rückruf zu schlafen.

+0

Danke Ben! Ich habe Ihr Proxy-Beispiel ein wenig weiter und erstellt eine Unblocking-Funktion. Sie können meine Implementierung hier sehen: http://goo.gl/8kbc6y –

Verwandte Themen