Quando iniciamos nossa caminhada na programação em R, é muito comum uma expressão que mistura surpresa e indignação ao descobrir que um trabalho de coleta de dados efetuado em 3 meses pode ser executado de forma automatizada pelo R em poucos minutos! Essa proeza é executada normalmente usando um loop for
que permite executar a mesma tarefa repetidas vezes.
Depois de algum tempo de uso e convivência com a comunidade, percebemos que o loop for
do R não possui um desempenho muito bom. Está mais para um fusquinha. Uma Ferrari seria a família de funções apply
, aliás uma das principais vantagens da linguagem R sobre outras linguagens, em minha opinião. A maioria das funções da família apply
foi escrita em C e tem um desempenho muito superior ao loop for
. Vamos efetuar uma rotina básica de cálculos usando um loop for e uma função da família apply para compará-los.
Digamos que você tem um banco de dados com um milhão de linhas (razoavelmente grande, heim…) e quer executar algumas transformações em uma das variáveis: multiplicar por 10 e tirar a raiz quadrada. Usando o comando system.time()
vamos verificar quanto tempo (em segundos) a máquina gasta para executar esses cálculos. Vale lembrar que estamos usando um notebook simples com processador core i3 e 4 Gb de RAM.
numeros <- 1:1000000
system.time({
teste1 = c()
for (x in numeros){
y = x*10
z = sqrt(y)
teste1 = c(teste1, z)
}
})
## user system elapsed
## 5427.453 418.447 5846.278
Hum… O tempo de processamento foi de aproximadamente 1h e 37 min (5846 segundos). Vamos ver agora o desempenho da máquina com a função sapply (sapply é uma função da família apply que retorna vetores simples ao invés de listas).
system.time({
teste2 = sapply(numeros, function(x){
y = x*10
z = sqrt(y)
return(z)}
)
})
## user system elapsed
## 3.113 0.026 3.139
Wow!!! Conseguimos reduzir 1 hora e meia para 3 segundos!!! Estamos super velozes!!!
Quando as instruções a serem executadas são mais complexas, esse tempo de processamento pode ser ainda grande. Há outra otimização desenvolvida pelos cientistas da computação muito útil: a computação paralela.
Por default, qualquer operação executada em R é feita de modo serial, ou seja, os cálculos acontecem um de cada vez. Se esse processo será repetido um milhão de vezes, para otimizá-lo podemos dividir os cálculos em 4 partes e enviar uma delas para cada núcleo do processador para serem executados simultaneamente. Isso pode reduzir drasticamente o tempo de processamento.
Não vamos apresentar aqui um exemplo complexo mas simplesmente a implementação do mesmo processo executado acima agora com computação paralela (talvez num próximo post…). Para isso, carregaremos o pacote parallel
e usaremos uma função muito parecida com a função sapply. Vamos ver como fica o tempo de processamento aqui.
library(parallel) # carrega o pacote
no_cores = detectCores() # define o número de núcleos a serem usados
cl = makeCluster(no_cores) # faz os clusters
# exporta para os cluster a variável a ser trabalhada
# sem esse comando os clusters não reconhecem a variável direto do global env.
clusterExport(cl, "numeros")
# Ação!
system.time({
teste3 = parSapply(cl, numeros, function(x){
y = x*10
z = sqrt(y)
return(z)}
)
})
stopCluster(cl) # interrompe os clusters
## user system elapsed
## 1.456 0.050 2.735
WOW!!! Os cálculos ficaram ainda mais rápidos!!! Agora estamos velozes e furiosos!!!
Ao executar tarefas complexas, o paralelismo pode ser uma grande ferramenta. Para um maior aprofundamento no tema, consulte este post. Comentários são sempre bem vindos! Grande abraço!