Почему задержка EventMachine медленнее, чем Ruby Thread? - PullRequest
10 голосов
/ 18 июня 2010

У меня есть два сценария, которые используют 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

Рубиновая нить:

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 ]

24 голосов
/ 02 августа 2012

Во всех ответах в этой теме отсутствует один ключевой момент: ваши обратные вызовы выполняются внутри потока реактора, а не в отдельном отложенном потоке. Выполнение запросов Mechanize при вызове defer - это правильный способ избежать блокировки цикла, но вы должны быть осторожны, чтобы ваш обратный вызов также не блокировал цикл.

Когда вы запускаете 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.

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 секунду для служебных данных.

9 голосов
/ 18 июня 2010

Да, вы используете это неправильно.EventMachine работает, выполняя асинхронные вызовы ввода-вывода, которые немедленно возвращаются и уведомляют «реактор» (цикл событий, запущенный EM.run), когда они завершены.У вас есть два блокирующих вызова, которые побеждают назначение системы: sleep и Mechanize.get.Вы должны использовать специальные асинхронные / неблокирующие библиотеки для получения любого значения из EventMachine.

7 голосов
/ 19 августа 2010

Вы должны использовать что-то вроде em-http-request http://github.com/igrigorik/em-http-request

2 голосов
/ 14 января 2012

EventMachine "defer" фактически порождает потоки Ruby из пула потоков, которым он управляет для обработки вашего запроса.Да, EventMachine предназначен для неблокирующих операций ввода-вывода, но команда defer является исключением - она ​​позволяет вам выполнять длительные операции без блокировки реактора.

Итак, он будет немного медленнее, чем голые потоки, потому что на самом деле он просто запускает потоки с накладными расходами диспетчера потоков EventMachine.

Подробнее об отсрочке можно прочитать здесь: http://eventmachine.rubyforge.org/EventMachine.html#M000486

Тем не менее, выборка страниц - это отличное применение EventMachine, но, как говорили другие авторы, вам нужно использовать неблокирующую библиотеку ввода-вывода, а затем использовать next_tick или аналогичный для запуска ваших задач, а не откладывать, которая нарушает вашу задачу из контура реактора.

...