George Vega
23 Abr 2014
Grupo de Usuarios de R en Chile
En esta presentación…
RcppRcpp
RcppPara poder utilizar Rcpp es necesario contar con lo siguiente:
R (>= 3.0.0)gcc (Incluido en Rtools)Adicionalmente se sugiere
inline (CRAN)rbenchmark (CRAN)Nos saltaremos la parte del típico 'Hola mundo' para pasar de lleno a Rcpp
Para eso partiremos con un ejemplo propuesto por uno de los creadores de Rcpp, Dirk Eddelbuettel
Considere la siguiente función
\[ f(n) = \begin{cases} n & \text{si $n<2$} \\ f(n-1) + f(n - 2) & \text{si $n\geq2$} \end{cases} \]
Su implementación en R sería
# Definiendo la funcion
f <- function(n) {
if (n<2) return(n)
return(f(n-1) + f(n-2))
}
# Aplicando la funcion a un vector de 0 a 9
sapply(0:9, f)
[1] 0 1 1 2 3 5 8 13 21 34
Y su equivalente en Rcpp
# Cargando Rcpp
library(Rcpp)
# Definiendo la funcion
cppFunction('
int g(int n) {
if (n < 2) return(n);
return(g(n-1) + g(n-2));
}'
)
# Aplicando la funcion a un vector de 0 a 9
sapply(0:9, g)
[1] 0 1 1 2 3 5 8 13 21 34
Y por qué la molestia?
Misma función… diferente desempeño!
library(rbenchmark)
benchmark(f(20), g(20))[,1:4]
test replications elapsed relative
1 f(20) 100 2.673 445.5
2 g(20) 100 0.006 1.0
Rcpp es al menos x400 más rápido!Lo que en Rcpp implica acceder a una función del paquete stats …
Environment stats("package:stats");
Function rnorm = stats["rnorm"];
return rnorm(10, Named("sd", 100.0));
… en la API de R equivale a un desastre!
SEXP stats = PROTECT(
R_FindNamespace(
mkString("stats")));
SEXP rnorm = PROTECT(
findVarInFrame(stats,
install("rnorm")));
SEXP call = PROTECT(
LCONS( rnorm,
CONS(ScalarInteger(10),
CONS(ScalarReal(100.0),
R_NilValue))));
SET_TAG(CDDR(call),install("sd"));
SEXP res = PROTECT(eval(call,
R_GlobalEnv));
UNPROTECT(4);
return res;
El paquete Rcpp permite compilar código a través de
tres funciones clave:
cppFunction Define funciones de Rcpp de manera dinámica.sourceCpp Ejecuta archivos .cpp, evalúa e importa funciones (además permite incorporar bloques de R).evalCpp Evalúa expresiones escritas en C++(Nota: En Rstudio presionar [Ctrl] + [Enter] equivale a sourceCpp)
Como cppFunction ya lo vimos, partiremos con
evalCpp
# Obteniendo constantes
evalCpp("PI")
[1] 3.142
evalCpp("std::numeric_limits<double>::max()")
[1] 1.798e+308
evalCpp("__cplusplus")
[1] 199711
Utilizando la etiqueta // [[Rcpp::export]], sourceCpp importa funciones escritas
en C++ a R de manera dinámica. Más aún, permite incluir bloques de código
de R.
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
int g(int n) {
if (n < 2) return(n);
return(g(n-1) + g(n-2));
}
/*** R
g(10)
***/
En lo que sigue, revisaremos las clases de objetos en Rcpp y como
manipularlos.
IntegerVector, NumericVector, LogicalVector, CharacterVector (Se accede con [i]).IntegerMatrix, NumericMatrix, … (Se accede con (i,j)).List (Se accede con _["objeto"]).Functionx <- -5:5
x
log(abs(x))
[1] -5 -4 -3 -2 -1 0 1 2 3 4 5
[1] 1.6094 1.3863 1.0986 0.6931 0.0000 -Inf 0.0000 0.6931 1.0986 1.3863
[11] 1.6094
rla <- function(x) {
for (i in 1:length(x)) {
x[i] <- log(abs(x[i]))
}
return(x)
}
rla(x)
[1] 1.6094 1.3863 1.0986 0.6931 0.0000 -Inf 0.0000 0.6931 1.0986 1.3863
[11] 1.6094
cppFunction("
NumericVector cppla1(NumericVector x) {
NumericVector out(x.size());
for(int i=0;i<x.size();i++)
out[i] = log(fabs(x[i]));
return out;
}")
cppla1(x)
[1] 1.6094 1.3863 1.0986 0.6931 0.0000 -Inf 0.0000 0.6931 1.0986 1.3863
[11] 1.6094
Utilizando sugar podemos escribirlo de una forma más sencilla!
cppFunction("
NumericVector cppla2(NumericVector x) {
return log(abs(x));
}")
cppla1(x)
[1] 1.6094 1.3863 1.0986 0.6931 0.0000 -Inf 0.0000 0.6931 1.0986 1.3863
[11] 1.6094
Algunas de las funciones incorporadas son abs(x), exp(x), floor(x), ceil(x), pow(x, z). (más en breve)
x <- 1:10000
rbenchmark::benchmark(log(abs(x)), rla(x),cppla1(x),cppla2(x))[,1:4]
test replications elapsed relative
3 cppla1(x) 100 0.061 1.271
4 cppla2(x) 100 0.048 1.000
1 log(abs(x)) 100 0.056 1.167
2 rla(x) 100 2.057 42.854
mimat <- matrix(1:9, nrow=3)
mimat
[,1] [,2] [,3]
[1,] 1 4 7
[2,] 2 5 8
[3,] 3 6 9
diag(mimat)
[1] 1 5 9
cppFunction("
NumericVector cppdiag(NumericMatrix x) {
NumericVector out(x.nrow());
for(int i=0;i<x.nrow();i++)
out[i] = x(i,i);
return out;
}")
cppdiag(mimat)
[1] 1 5 9
Crearemos una lista con números aleatorios un vector char y un vector 'al cuadrado'
rlist <- function(x,y,z) {
return(list(
x=rnorm(x),
y=y,
z=z^2
))
}
Su equivalente en Rcpp (una vez mas incluyendo sugar)
cppFunction('
List cpplist(
int x, CharacterVector y, NumericVector z) {
return List::create(
_["x"]=rnorm(x,0,1),
_["y"]=y,
_["z"]=pow(z,2) /* sugar */
);
}')
set.seed(123)
rlist(3, c("a","hola","Si"),
seq(-3,3))
$x
[1] -0.5605 -0.2302 1.5587
$y
[1] "a" "hola" "Si"
$z
[1] 9 4 1 0 1 4 9
set.seed(123)
cpplist(3, c("a","hola","Si"),
seq(-3,3))
$x
[1] -0.5605 -0.2302 1.5587
$y
[1] "a" "hola" "Si"
$z
[1] 9 4 1 0 1 4 9
# Funcion que llama funciones
rf <- function(x,f) {
for(i in 1:100)
out <- f(x)
return(out)
}
# Ejemplos
rf(c(1,4,9), sqrt)
rf(c(1,4,9), function(x) 1/x)
[1] 1 2 3
[1] 1.0000 0.2500 0.1111
# Funcion que llama funciones
cppFunction('
NumericVector cppf(NumericVector x, Function f) {
NumericVector out(x.length());
for(int i=0;i<100;i++)
out=f(x);
return out;
}')
# Ejemplos
cppf(c(1,4,9), sqrt)
cppf(c(1,4,9), function(x) 1/x)
[1] 1 2 3
[1] 1.0000 0.2500 0.1111
Con Rcpp es posible acelerar los tiempos de cómputo
de funciones escritas por el usuario en bucles.
x <- 1:3
f <- sqrt
benchmark(rf(x,f), cppf(x, f))[,1:4]
test replications elapsed relative
2 cppf(x, f) 100 0.148 37
1 rf(x, f) 100 0.004 1
Rcpp cuenta con 9 vignettes.George Vega (g.vegayon@gmail.com)
Grupo de Usuarios de R en Chile, 23 Abr 2014
(presentación creada con Rstudio + knitr)