R Language
Beschleunigen Sie Code, der stark zu vektorisieren ist
Suche…
Beschleunigen Sie mit Rcpp die zähste Vektorisierung für Loops
Betrachten Sie die folgende hard-to-vectorize for-Schleife, die einen Vektor der Länge len
in dem das erste Element angegeben ist ( first
Element) und jedes Element x_i
gleich 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)
}
Dieser Code beinhaltet eine for-Schleife mit einer schnellen Operation ( cos(x[i-1]+1)
), die häufig von der Vektorisierung profitiert. Es ist jedoch nicht trivial, diese Operation mit der Basis R zu vektorisieren, da R keine Funktion "kumulativer Cosinus von x + 1" hat.
Ein möglicher Ansatz, um diese Funktion zu beschleunigen, wäre die Implementierung in C ++ mithilfe des Rcpp-Pakets:
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;
}")
Dies führt häufig zu erheblichen Beschleunigungen für große Berechnungen, wobei dieselben Ergebnisse erzielt werden:
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 diesem Fall generiert der Rcpp-Code in 0,03 Sekunden einen Vektor mit einer Länge von 1 Million anstelle von 1,31 Sekunden mit dem Ansatz der Basis-R.
Beschleunigung für das Schleifen von Bytes durch Kompilieren
Betrachten Sie nach dem Rcpp-Beispiel in diesem Dokumentationseintrag die folgende Tough-to-Vectorize-Funktion, die einen Vektor der Länge len
wobei das erste Element angegeben wird ( first
Element) und jedes Element x_i
gleich 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)
}
Ein einfacher Ansatz, um eine solche Funktion zu beschleunigen, ohne eine einzelne Codezeile umzuschreiben, ist das Bytekompilieren des Codes mithilfe des R-Compile-Pakets:
library(compiler)
repeatedCosPlusOneCompiled <- cmpfun(repeatedCosPlusOne)
Die sich ergebende Funktion ist oft wesentlich schneller, liefert aber immer noch die gleichen Ergebnisse:
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 diesem Fall beschleunigte das Byte-Kompilieren die Vektorisierung der Vektorisierung mit einem Vektor der Länge 1 Million von 1,20 Sekunden auf 0,34 Sekunden.
Anmerkung
Das Wesen von repeatedCosPlusOne
als kumulative Anwendung einer einzelnen Funktion kann mit Reduce
transparenter ausgedrückt werden:
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
kann als "Vektorisierung" von repeatedCosPlusOne
. Es kann jedoch erwartet werden, dass es um den Faktor 2 langsamer ist :
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