R 3.4 disponível!

Por Julio 24/04/2017

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:

  1. 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.
  2. O for ficou mais eficiente. Agora a alocação dinâmica de vetores está mais rápida, diminuindo ainda mais a diferença entre utilizar for e funcionais como sapply(), que trabalham com um vetor pré alocado. Mais sobre isso abaixo.
  3. Otimizações em operações com matrizes. Não vou entrar em detalhes, mas talvez vocês notem algumas melhorias.
  4. 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.

R mais rápido! Imagem emprestada do time do [Rcpp](https://github.com/RcppCore).

Figure 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: 0x48b4d20>

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!

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

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

comments powered by Disqus