R Language
Moeilijk te vectoriseren code versnellen
Zoeken…
Snelheid moeilijk te vectoriseren voor lussen met Rcpp
Overweeg de volgende moeilijk te vectoriseren voor lus, die een vector van lengte len
creëert waarbij het eerste element is opgegeven ( first
) en elk element x_i
gelijk is aan 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)
}
Deze code omvat een for-lus met een snelle werking ( cos(x[i-1]+1)
), die vaak profiteren van vectorisatie. Het is echter niet triviaal om deze bewerking te vectoriseren met base R, omdat R geen "cumulatieve cosinus van x + 1" functie heeft.
Een mogelijke manier om deze functie te versnellen zou zijn om deze in C ++ te implementeren met behulp van het Rcpp-pakket:
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;
}")
Dit levert vaak aanzienlijke versnellingen op voor grote berekeningen en levert exact dezelfde resultaten op:
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
In dit geval genereert de Rcpp-code een vector met een lengte van 1 miljoen in 0,03 seconden in plaats van 1,31 seconden met de basis R-benadering.
Snelheid moeilijk te vectoriseren voor lussen per byte compileren
Volg het Rcpp-voorbeeld in deze documentatie-entry en overweeg de volgende moeilijk te vectoriseren functie, die een vector van lengte len
creëert waarbij het eerste element is opgegeven ( first
) en elk element x_i
gelijk is aan 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)
}
Een eenvoudige manier om een dergelijke functie te versnellen zonder een enkele coderegel te herschrijven, is byte compileren van de code met behulp van het R compile-pakket:
library(compiler)
repeatedCosPlusOneCompiled <- cmpfun(repeatedCosPlusOne)
De resulterende functie zal vaak aanzienlijk sneller zijn en toch dezelfde resultaten opleveren:
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
In dit geval versnelde het compileren van bytes de moeilijk te vectoriseren bewerking op een vector met een lengte van 1 miljoen van 1,20 seconden tot 0,34 seconden.
Opmerking
De essentie van repeatedCosPlusOne
, als de cumulatieve toepassing van een enkele functie, kan transparanter worden uitgedrukt met 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 worden beschouwd als een "vectorisatie" van repeatedCosPlusOne
. Er kan echter worden verwacht dat het met een factor 2 langzamer is:
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