为什么EventMachine比Ruby Thread更慢?

时间:2010-06-17 23:34:12

标签: ruby eventmachine

我有两个使用Mechanize来获取Google索引页面的脚本。我假设EventMachine比Ruby线程更快,但事实并非如此。

EventMachine代码费用:"0.24s user 0.08s system 2% cpu 12.682 total"

Ruby线程代码费用:"0.22s user 0.08s system 5% cpu 5.167 total "

我是否以错误的方式使用EventMachine?

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

Ruby Thread:

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

4 个答案:

答案 0 :(得分:24)

此主题中的所有答案都缺少一个关键点:您的回调正在反应器线程内运行,而不是在单独的延迟线程中运行。在defer调用中运行Mechanize请求是阻止循环的正确方法,但您必须小心,您的回调不会阻止循环。

运行EM.defer operation, callback时,操作在Ruby生成的线程内运行,该线程完成工作,然后在主循环内发出回调。因此,sleep 1中的operation并行运行,但回调串行。这解释了运行时间差异接近9秒。

以下是您正在运行的代码的简化版本。

EM.run {
  times = 0

  work = proc { sleep 1 }

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

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

大约需要12秒,并行睡眠为1秒,串行睡眠为10秒,开销为1秒。

要并行运行回调代码,您必须使用代理回调为其生成新线程,使用EM.defer,如下所示:

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

但是,如果你的回调被认为是在事件循环中执行代码,那么你可能会遇到这个问题,因为它是在一个单独的延迟线程中运行的。如果发生这种情况,请将问题代码移到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 }
}

该版本在大约3秒钟内运行,占并行休眠1秒,并行回拨1秒,开销1秒。

答案 1 :(得分:9)

是的,你错了。 EventMachine通过进行立即返回的异步IO调用并在完成后通知“reactor”(由EM.run启动的事件循环)来工作。你有两个阻塞调用,打破了系统的目的,sleep和Mechanize.get。您必须使用特殊的异步/非阻塞库来从EventMachine派生任何值。

答案 2 :(得分:7)

您应该使用类似em-http-request http://github.com/igrigorik/em-http-request

的内容

答案 3 :(得分:2)

EventMachine“defer”实际上是从它设法处理您的请求的线程池中生成Ruby线程。是的,EventMachine是为非阻塞IO操作而设计的,但是defer命令是一个例外 - 它旨在允许您在不阻塞反应器的情况下执行长时间运行的操作。

所以,它会比裸线程慢一点,因为实际上它只是用EventMachine的线程池管理器的开销来启动线程。

您可以在此处详细了解:http://eventmachine.rubyforge.org/EventMachine.html#M000486

也就是说,抓取页面是一个很好的使用EventMachine,但正如其他海报所说,你需要使用一个非阻塞的IO库,然后使用next_tick或类似的来启动你的任务,而不是推迟,哪个打破你的任务离开反应堆循环。