Почему 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 секунда для накладных расходов.

Ответ 4

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

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

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

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