Por que o adiamento de EventMachine é mais lento que um fio de rubi?
-
28-09-2019 - |
Pergunta
Eu tenho dois scripts que usam Mechanize para buscar uma página de índice do Google. Eu assumi que a EventMachine seria mais rápida que um fio de rubi, mas não é.
Custos do código do EventMachine: "0.24s user 0.08s system 2% cpu 12.682 total"
CÓDIGOS DE CÓDIGOS DE LINHAS RUBY: "0.22s user 0.08s system 5% cpu 5.167 total "
Estou usando o EventMachine da maneira errada?
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
Solução
Sim, você está usando errado. A EventMachine funciona fazendo chamadas de IO assíncronas que retornam imediatamente e notificam o "reator" (o loop do evento iniciado por em.run) quando eles são concluídos. Você tem duas chamadas de bloqueio que derrotam o objetivo do sistema, dormir e mecanizar.get. Você precisa usar bibliotecas especiais assíncronas/não bloqueadoras para derivar qualquer valor da EventMachine.
Outras dicas
Todas as respostas neste thread estão faltando um ponto -chave: seus retornos de chamada estão sendo executados dentro do encadeamento do reator, em vez de em um encadeamento diferido separado. Executando solicitações mecanizadas em um defer
A chamada é a maneira certa de não bloquear o loop, mas você deve ter cuidado para que seu retorno de chamada também não bloqueie o loop.
Quando você corre EM.defer operation, callback
, a operação é executada dentro de um fio de rubi, que faz o trabalho e, em seguida, o retorno de chamada é emitido dentro do loop principal. Portanto, o sleep 1
dentro operation
corre em paralelo, mas o retorno de chamada é em série. Isso explica a diferença próxima de 9 segundos no tempo de execução.
Aqui está uma versão simplificada do código que você está executando.
EM.run {
times = 0
work = proc { sleep 1 }
callback = proc {
sleep 1
EM.stop if (times += 1) >= 10
}
10.times { EM.defer work, callback }
}
Isso leva cerca de 12 segundos, que é 1 segundo para o sono paralelo, 10 segundos para o serial dorme e 1 segundo para sobrecarga.
Para executar o código de retorno de chamada em paralelo, você deve gerar novos threads usando um retorno de chamada proxy que usa EM.defer
igual a:
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 }
}
No entanto, você pode ter problemas com isso se o seu retorno de chamada deve executar o código no loop do evento, porque ele é executado dentro de um encadeamento adiado e separado. Se isso acontecer, mova o código do problema para o retorno de chamada do 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 }
}
Esta versão foi executada em cerca de 3 segundos, que representam 1 segundo de sono para operação em paralelo, 1 segundo de dormir para retorno de chamada em paralelo e 1 segundo para sobrecarga.
Você deve usar algo como em-http-request http://github.com/igrigorik/em-http-request
A EventMachine "adiar" realmente gera rosca de rubi de um threadpool que ele consegue lidar com sua solicitação. Sim, o EventMachine foi projetado para operações de IO não bloqueador, mas o comando adie é uma exceção - ele foi projetado para permitir que você faça operações de longa execução sem bloquear o reator.
Então, será um pouco mais lento que os fios nus, porque na verdade está apenas lançando tópicos com a sobrecarga do gerente de Threadpool da EventMachine.
Você pode ler mais sobre adiar aqui: http://eventmachine.rubyforge.org/eventmachine.html#m000486
Dito isto, buscar páginas é um ótimo uso da EventMachine, mas como outros pôsteres disseram, você precisa usar uma biblioteca de IO não bloqueada e depois usar o Next_Tick ou semelhante para iniciar suas tarefas, em vez de adiar, o que divide sua tarefa do loop do reator.