Programadores eficientes não precisam escrever algoritmos que rodam rápido. Recomendo fortemente a leitura do livro Efficient R, que discute eficiência com o R de forma exaustiva. Também gosto muito da primeira parte dessa palestra do Hadley, onde ele defende que o cientista de dados deve usar seu tempo pensando no problema e não na forma que vai escrever seu código.
Com isso em mente, vamos investigar o tema paralelização. Quando rodamos coisas em paralelo, mandamos os núcleos de processamento da máquina calcularem coisas diferentes ao mesmo tempo. A vantagem disso é que o tempo de execução dos algoritmos é dividido pelo número de núcleos disponíveis, sem exigir grandes mudanças no código utilizado.
Vamos mostrar como paralelizar um código usando a função llply()
do pacote plyr
. Essa função funciona de forma idêntica ao lapply()
, ou seja, recebe uma lista ou vetor como input, aplica uma função em cada elemento, e retorna os resultados numa lista com o mesmo comprimento.
A função dormir()
manda o R esperar seg
segundos antes de concluir, retornando seg
.
dormir <- function(seg = 1) {
Sys.sleep(seg)
return(seg)
}
É intuitivo afirmar que o tempo de execução de dormir()
é compatível com seg
.
system.time({
dormir()
})
## user system elapsed
## 0.000 0.000 1.005
Nosso interesse é aplicar dormir()
em cada elemento do vetor c(1, 2)
. Esse algoritmo demora 1 + 2 = 3
segundos.
segundos <- c(1, 2)
system.time({
plyr::llply(segundos, dormir)
})
## user system elapsed
## 0.054 0.005 3.071
Agora vamos executar o mesmo código usando paralelização. Antes, precisamos
- criar e registrar as cópias de R que rodam em paralelo; e
- adicionar o parâmetro
.parallel = TRUE
nollply()
O primeiro passo é resolvido com os pacotes parallel
e doParallel
. Veja como fica o código:
cl <- parallel::makePSOCKcluster(2) # cria as cópias do R que rodam em paralelo
doParallel::registerDoParallel(cl) # registra as cópias do R para serem usadas no plyr
system.time({
plyr::llply(segundos, dormir, .parallel = TRUE)
})
## user system elapsed
## 0.012 0.000 2.067
O tempo total de execução foi de ~2.2 segundos, um pouco mais do que dormir(2)
. Os dois décimos de segundo adicionais são necessários para preparar o terreno da paralelização. Inclusive, se você rodar o código em paralelo novamente, o tempo adicional cai para quase nada:
system.time({
plyr::llply(segundos, dormir, .parallel = TRUE)
})
## user system elapsed
## 0.005 0.001 2.011
Se quiser parar de rodar coisas em paralelo, basta rodar stopCluster()
:
parallel::stopCluster(cl) # para de rodar coisas em paralelo
E é isso, caros errantes. Rappy coding :)