Почему EventMachine откладывает медленнее, чем Ruby Thread?
У меня есть два сценария, которые используют Mechanize для получения индексной страницы Google. Я предположил, что EventMachine будет быстрее, чем поток Ruby, но это не так.
Стоимость кода EventMachine: "0.24s user 0.08s system 2% cpu 12.682 total"
Стоимость кода Ruby Thread: "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
Ответы
Ответ 1
Да, вы используете это неправильно. EventMachine работает, создавая асинхронные вызовы ввода-вывода, которые немедленно возвращаются и уведомляют "реактор" (цикл событий, запущенный EM.run), когда они завершены. У вас есть два блокирующих вызова, которые поражают цель системы, сон и Mechanize.get. Вы должны использовать специальные асинхронные/неблокирующие библиотеки для получения любого значения из EventMachine.
Ответ 2
В ответах в этой теме отсутствует один ключевой момент: ваши обратные вызовы выполняются внутри реакторной нити, а не в отдельной отложенной нити. Выполнение запросов Механизировать в вызове 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 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 секунда для накладных расходов.
Ответ 3
Вы должны использовать что-то вроде em-http-request http://github.com/igrigorik/em-http-request
Ответ 4
EventMachine "defer" фактически порождает потоки Ruby из потока, которым он управляет, чтобы обрабатывать ваш запрос. Да, EventMachine предназначен для неблокирующих операций ввода-вывода, но команда отсрочки является исключением - она предназначена для того, чтобы вы могли выполнять длительные операции без блокировки реактора.
Итак, это будет немного медленнее, чем голые потоки, потому что на самом деле он просто запускает потоки с накладными расходами диспетчера потоков EventMachine.
Подробнее об отсрочке можно прочитать здесь: http://eventmachine.rubyforge.org/EventMachine.html#M000486
Тем не менее, выборка страниц - отличное использование EventMachine, но, как говорили другие плакаты, вам нужно использовать неблокирующую IO-библиотеку, а затем использовать next_tick или подобное для запуска ваших задач, а не отложить, что перерывы ваша задача из петли реактора.