R Language
Przyspieszenie trudnego do wektoryzacji kodu
Szukaj…
Przyspieszanie trudnej do wektoryzacji pętli za pomocą Rcpp
Rozważ następującą trudną do wektoryzacji pętlę, która tworzy wektor długości len
którym określony jest pierwszy element ( first
), a każdy element x_i
jest równy 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)
}
Ten kod obejmuje pętlę for z szybką operacją ( cos(x[i-1]+1)
), która często korzysta z wektoryzacji. Jednak wektoryzacja tej operacji z zasadą R nie jest trywialna, ponieważ R nie ma funkcji „kumulatywnego cosinusa x + 1”.
Jednym z możliwych podejść do przyspieszenia tej funkcji byłoby zaimplementowanie jej w C ++ przy użyciu pakietu 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;
}")
Zapewnia to często znaczne przyspieszenie dużych obliczeń, przynosząc dokładnie takie same wyniki:
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
W takim przypadku kod Rcpp generuje wektor o długości 1 miliona w 0,03 sekundy zamiast 1,31 sekundy w przypadku podstawowego podejścia R.
Przyspieszanie trudnej do wektoryzacji pętli przez kompilację bajtów
Zgodnie z przykładem Rcpp w tym wpisie dokumentacji, rozważ następującą trudną do wektoryzacji funkcję, która tworzy wektor długości len
którym określony jest pierwszy element ( first
), a każdy element x_i
jest równy 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)
}
Jednym prostym podejściem do przyspieszenia takiej funkcji bez przepisywania jednego wiersza kodu jest kompilacja bajtu przy użyciu pakietu kompilacji R:
library(compiler)
repeatedCosPlusOneCompiled <- cmpfun(repeatedCosPlusOne)
Wynikowa funkcja często będzie znacznie szybsza, przy jednoczesnym zwracaniu tych samych wyników:
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
W tym przypadku kompilacja bajtów przyspieszyła trudną do wektorowania operację na wektorze o długości 1 miliona od 1,20 sekundy do 0,34 sekundy.
Uwaga
Istotę repeatedCosPlusOne
, jako skumulowanego zastosowania pojedynczej funkcji, można wyrazić w bardziej przejrzysty sposób dzięki funkcji 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
może być traktowane jako „wektoryzacji” z repeatedCosPlusOne
. Można jednak oczekiwać, że będzie wolniejszy 2 razy:
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