A versão 3.4 do R (You Stupid Darkness) foi lançada nesse final de semana! A atualização tem foco principal na performance. Principais mudanças:
- Agora temos um compilador JIT (Just In Time) como padrão! Isso significa que você não precisará mais usar a função
compiler::cmpfun()
para acelerar suas funções na maioria dos casos. Mais sobre isso abaixo. - O
for
ficou mais eficiente. Agora a alocação dinâmica de vetores está mais rápida, diminuindo ainda mais a diferença entre utilizarfor
e funcionais comosapply()
, que trabalham com um vetor pré alocado. Mais sobre isso abaixo. - Otimizações em operações com matrizes. Não vou entrar em detalhes, mas talvez vocês notem algumas melhorias.
- Agora o método padrão para ordenar vetores é
radix
, o que pode aumentar a velocidade de ordenações para vetores com mais de mil entradas.
Para uma lista completa de mudanças, veja esse post.
.](/images/posts/banner/fastr.webp)
Figura 1: R mais rápido! Imagem emprestada do time do Rcpp.
Mais sobre o JIT compiler
Veja esse exemplo extraído do livro Efficient R. Observe a existência de um <bytecode ...>
na parte de baixo de uma das funções. Isso significa que o pacote compiler
converteu essa função em um código que pode ser interpretado mais rápido.
mean_r = function(x) {
m = 0
n = length(x)
for(i in seq_len(n))
m = m + x[i] / n
m
}
cmp_mean_r <- compiler::cmpfun(mean_r)
mean_r
## function(x) {
## m = 0
## n = length(x)
## for(i in seq_len(n))
## m = m + x[i] / n
## m
## }
cmp_mean_r
## function(x) {
## m = 0
## n = length(x)
## for(i in seq_len(n))
## m = m + x[i] / n
## m
## }
## <bytecode: 0x7fb86252dfd8>
Para mostrar a mudança, vamos comparar o desempenho das funções usando microbenchmark()
. Essa função calcula o tempo de execução de uma expressão cem vezes e calcula estatísticas básicas dos tempos obtidos.
No meu servidor, que ainda está com o R 3.3.2, o resultado foi esse. Observe que o tempo da função compilada é quase dez vezes o tempo da função sem compilar.
set.seed(1)
x <- rnorm(5000)
microbenchmark::microbenchmark(mean_r(x), cmp_mean_r(x), mean(x))
# Unit: microseconds
# expr min lq mean median uq max neval
# mean_r(x) 1931.835 2010.295 2302.82298 2102.5995 2357.6715 6186.706 100
# cmp_mean_r(x) 308.847 311.045 333.26221 314.8935 334.7330 569.117 100
# mean(x) 14.593 15.443 19.94897 19.0410 21.0405 51.375 100
No meu computador com o R 3.4, o resultado foi esse. Agora, a diferença entre a função sem compilar e compilada é praticamente impoerceptível. Esse é o efeito do JIT compiler.
set.seed(1)
x <- rnorm(5000)
microbenchmark::microbenchmark(mean_r(x), cmp_mean_r(x), mean(x))
# Unit: microseconds
# expr min lq mean median uq max neval
# mean_r(x) 332.322 332.7220 336.2287 333.0125 334.3785 395.954 100
# cmp_mean_r(x) 332.305 332.7345 337.0889 333.1460 337.0930 381.306 100
# mean(x) 13.807 14.0960 14.7349 14.3060 14.5540 30.313 100
Mais sobre o for
Veja esse código que calcula a média de mil valores em 100 entradas de uma lista.
set.seed(1)
input <- lapply(1:100, function(x) runif(1000))
mean_for <- function(x) {
vet <- c()
for(i in seq_along(x)) {
vet[i] <- mean(x[[i]])
}
}
mean_sapply <- function(x) {
sapply(x, mean)
}
No meu servidor, que ainda está com o R 3.3.2, o resultado foi esse. Veja como o desempenho do for
é assustadoramente inferior.
microbenchmark::microbenchmark(mean_for(x), mean_sapply(x))
# Unit: milliseconds
# expr min lq mean median uq max neval
# mean_for(x) 41.28675 43.22318 47.39574 44.02713 45.59184 84.80818 100
# mean_sapply(x) 14.78590 15.46421 16.36619 16.23018 17.28495 19.70854 100
No meu computador com o R 3.4, o resultado foi esse. Agora o for
está praticamente empatado!
microbenchmark::microbenchmark(mean_for(x), mean_sapply(x))
# Unit: milliseconds
# expr min lq mean median uq max neval
# mean_for(x) 15.16583 15.45924 16.71064 16.14545 17.51002 25.61860 100
# mean_sapply(x) 14.43704 14.90485 16.20864 15.53319 16.56801 27.36536 100
Mas cuidado! o for
continua sendo uma ideia ruim no R, não só por desempenho, mas por questões de design. Utilizar funcionais ajuda na performance do computador e torna a vida do cientista de dados mais fácil (veja esse e esse posts que discutem um pouco sobre isso.)
Instalação
Se você usa Windows, uma dica é usar o pacote installr
. Basta rodar isso aqui e ser feliz:
install.packages("installr")
installr::updateR()
É isso! Happy coding ;)
OBS: Se você ficou curiosa sobre o nome da versão, encontrei essa tirinha de 1965 do Peanuts. Acho que foi isso que deu origem ao nome!

Figura 2: You Stupid Darknes! http://www.gocomics.com/peanuts/1965/09/09.