Sök…


Påskynda för svår att vektorisera för slingor med Rcpp

Tänk på följande tufft att vektorisera för slinga, som skapar en vektor med längden len där det första elementet anges ( first ) och varje element x_i är lika med 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)
}

Denna kod involverar en for-loop med en snabb operation ( cos(x[i-1]+1) ), som ofta har nytta av vektorisering. Det är emellertid inte trivialt att vektorisera denna operation med bas R, eftersom R inte har en "kumulativ cosinus med x + 1" -funktion.

En möjlig metod för att påskynda denna funktion skulle vara att implementera den i C ++ med Rcpp-paketet:

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;
}")

Detta ger ofta betydande hastigheter för stora beräkningar och ger exakt samma resultat:

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 

I detta fall genererar Rcpp-koden en vektor med en längd på 1 miljon på 0,03 sekunder istället för 1,31 sekunder med bas R-metoden.

Påskynda tuff-till-vektorisera för slingor genom byte-sammanställning

Följ Rcpp-exemplet i den här dokumentationsposten, överväg följande tuff-till-vektoriseringsfunktion, som skapar en vektor med längd len där det första elementet anges ( first ) och varje element x_i är lika med 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)
}

Ett enkelt tillvägagångssätt för att påskynda en sådan funktion utan att skriva om en enda kodrader är att byta samman koden med R-kompileringspaketet:

library(compiler)
repeatedCosPlusOneCompiled <- cmpfun(repeatedCosPlusOne)

Den resulterande funktionen är ofta betydligt snabbare medan du fortfarande ger samma resultat:

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 

I detta fall rusade byte-kompilering upp hård-till-vektoriseringsoperationen på en vektor med längden 1 miljon från 1,20 sekunder till 0,34 sekunder.

Anmärka

Kärnan i repeatedCosPlusOne , som den kumulativa tillämpningen av en enda funktion, kan uttryckas mer öppet med 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 kan betraktas som en "vektorisering" av repeatedCosPlusOne . Det kan dock förväntas bli långsammare med en faktor 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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow