Recherche…


Accélérer la vectorisation des boucles avec Rcpp

Considérons la boucle difficile à vectoriser suivante, qui crée un vecteur de longueur len où le premier élément est spécifié (en first ) et chaque élément x_i est égal à cos(x_{i-1} + 1) :

repeatedCosPlusOne <- function(first, len) {
  x <- numeric(len)
  x[1] <- first
  for (i in 2:len) {
    x[i] <- cos(x[i-1] + 1)
  }
  return(x)
}

Ce code implique une boucle for avec une opération rapide ( cos(x[i-1]+1) ), qui bénéficie souvent de la vectorisation. Cependant, il n'est pas trivial de vectoriser cette opération avec la base R, puisque R n'a pas de fonction "cosinus cumulatif de x + 1".

Une approche possible pour accélérer cette fonction serait de l'implémenter en C ++, en utilisant le package Rcpp:

library(Rcpp)
cppFunction("NumericVector repeatedCosPlusOneRcpp(double first, int len) {
  NumericVector x(len);
  x[0] = first;
  for (int i=1; i < len; ++i) {
    x[i] = cos(x[i-1]+1);
  }
  return x;
}")

Cela fournit souvent des accélérations significatives pour les gros calculs tout en donnant les mêmes résultats:

all.equal(repeatedCosPlusOne(1, 1e6), repeatedCosPlusOneRcpp(1, 1e6))
# [1] TRUE
system.time(repeatedCosPlusOne(1, 1e6))
#    user  system elapsed 
#   1.274   0.015   1.310 
system.time(repeatedCosPlusOneRcpp(1, 1e6))
#    user  system elapsed 
#   0.028   0.001   0.030 

Dans ce cas, le code Rcpp génère un vecteur de longueur 1 million en 0,03 seconde au lieu de 1,31 seconde avec l'approche base R.

Accélérer la vectorisation des boucles en compilant les octets

En suivant l'exemple de Rcpp dans cette entrée de documentation, considérez la fonction difficile à vectoriser suivante, qui crée un vecteur de longueur len où le premier élément est spécifié (en first ) et chaque élément x_i est égal à cos(x_{i-1} + 1) :

repeatedCosPlusOne <- function(first, len) {
  x <- numeric(len)
  x[1] <- first
  for (i in 2:len) {
    x[i] <- cos(x[i-1] + 1)
  }
  return(x)
}

Une approche simple pour accélérer une telle fonction sans réécriture d'une seule ligne de code consiste à compiler l'octet en utilisant le package de compilation R:

library(compiler)
repeatedCosPlusOneCompiled <- cmpfun(repeatedCosPlusOne)

La fonction résultante sera souvent beaucoup plus rapide tout en renvoyant les mêmes résultats:

all.equal(repeatedCosPlusOne(1, 1e6), repeatedCosPlusOneCompiled(1, 1e6))
# [1] TRUE
system.time(repeatedCosPlusOne(1, 1e6))
#    user  system elapsed 
#   1.175   0.014   1.201 
system.time(repeatedCosPlusOneCompiled(1, 1e6))
#    user  system elapsed 
#   0.339   0.002   0.341 

Dans ce cas, la compilation des octets a accéléré l'opération difficile à vectoriser sur un vecteur de longueur 1 million de 1,20 seconde à 0,34 seconde.

Remarque

L'essence de repeatedCosPlusOne , en tant qu'application cumulative d'une fonction unique, peut être exprimée de manière plus transparente avec Reduce :

iterFunc <- function(init, n, func) {
  funcs <- replicate(n, func)
  Reduce(function(., f) f(.), funcs, init = init, accumulate = TRUE)
}
repeatedCosPlusOne_vec <- function(first, len) {
  iterFunc(first, len - 1, function(.) cos(. + 1))
}

repeatedCosPlusOne_vec peut être considéré comme une "vectorisation" de repeatedCosPlusOne . Cependant, on peut s'attendre à ce qu'il soit plus lent d'un facteur 2:

library(microbenchmark)
microbenchmark(
  repeatedCosPlusOne(1, 1e4),
  repeatedCosPlusOne_vec(1, 1e4)
)
#> Unit: milliseconds
#>                              expr       min        lq     mean   median       uq      max neval cld
#>      repeatedCosPlusOne(1, 10000)  8.349261  9.216724 10.22715 10.23095 11.10817 14.33763   100  a 
#>  repeatedCosPlusOne_vec(1, 10000) 14.406291 16.236153 17.55571 17.22295 18.59085 24.37059   100   b


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow