3. Rcpp basics
Rcpp via R CMD SHLIB: the old but instructive way
To illustrate how Rcpp
works, let us consider again our C function for exponential smoothing, that is:
#include <R.h>
#include <Rinternals.h>
SEXP expSmooth2(SEXP y, SEXP a)
{
int ni;
double *xy, *xys;
double ai;
SEXP ys;
y = PROTECT(coerceVector(y, REALSXP));
ni = length(y);
ys = PROTECT(allocVector(REALSXP, ni));
ai = REAL(a)[0];
xy = REAL(y);
xys = REAL(ys);
xys[0] = xy[0];
for(int i = 1; i < ni; i++){
xys[i] = ai * xys[i-1] + (1 - ai) * xy[i];
}
UNPROTECT(2);
return ys;
}
Now, the same function can be implemented in “raw” Rcpp as follows (as we will explain later, this is NOT the recommended way of using Rcpp
):
#include <Rcpp.h>
using namespace Rcpp;
RcppExport SEXP expSmoothRcpp_manual(SEXP ySEXP, SEXP aSEXP)
{
const NumericVector y = as<const NumericVector>(ySEXP);
const double a = as<const double>(aSEXP);
int ni = y.size();
NumericVector ys(ni);
ys[0] = y[0];
for(int i = 1; i < ni; i++){
ys[i] = a * ys[i-1] + (1 - a) * y[i];
}
return ys;
}
Don’t worry if you don’t understand what is going on in the code above, we’ll explain it below. As you can see the Rcpp
function for exponential smoothing is much more compact. On the Linux command line it can be compiled by doing:
library(Rcpp)
system("export PKG_CXXFLAGS=`Rscript -e \"Rcpp:::CxxFlags()\"` &&
R CMD SHLIB expSmoothRcpp_manual.cpp")
Note that the call to R CMD SHLIB
is preceded by some extra code, which is needed to let SHLIB
know where Rcpp.h
can be found. We then load the compiled code as usual:
dyn.load("expSmoothRcpp_manual.so")
Let’s see whether the new function actually works:
system("R CMD SHLIB expSmooth2.c")
dyn.load("expSmooth2.so")
y <- rnorm(100)
max( abs(.Call("expSmoothRcpp_manual", y, 0.9) - .Call("expSmooth2", y, 0.9)) )
## [1] 0
So the C
and Rcpp
functions produce exactly the same output.
Now, let us try to understand how the Rcpp
function for exponential smoothing works:
- in the first two lines we are including the
Rcpp.h
header and we are adopting the corresponding namespace. expSmoothRcpp_manual
hasSEXP
inputs and outputs, hence it can be called via.Call
.RcppExport
is an alias forextern "C"
, the reason why we needed the latter is not important here, but it is there to tell the C++ compiler not to mangle the name of the function, otherwise.Call
would not be able to find it.const NumericVector y = as<const NumericVector>(ySEXP);
converts theySEXP
argument, which has classSEXP
, to a constant object,y
, of classNumericVector
, which simply represents vectors of doubles. TheNumericVector
class and the templated functionas
belong to theRcpp
namespace. Importantly,ySEXP
is not copied, and modifyingy
would modifyySEXP
. We avoid this risk by usingconst
.const double a = as<const double>(aSEXP);
convertsaSEXP
similarly.int ni = y.size();
here we are using thesize
member function provided by theNumericVector
class to extract the size (or length) ofy
and store it into an integerni
.NumericVector ys(ni);
creates a newNumericVector
ys
of sizeni
.- Then the
for
loop is very similar to the one inexpSmooth2
and we return the smoothed vectorys
, without callingUNPROTECT
.
It is worth repeating that compiling Rcpp
code via R CMD SHLIB
is not recommended, and the next section will illustrate some better methods. The main advantage of Rcpp
illustrated so far is that we didn’t explicitly create any object of class SEXP
, which means that we didn’t have to call PROTECT
and UNPROTECT
to do memory management. In fact, this was done automatically for us via the NumericVector
class, which wraps an R
object of class SEXP
and protects it from R
’s garbage collector while it is in scope (that is, until we exit the part of the program where the
variable is accessible). Hence, our code is shorter and one source of mistakes (i.e., incorrect memory management) has been removed. Rcpp
provides many other wrappers around R
objects, such as:
NumericMatrix
wraps anumeric
matrix inR
;IntegerVector
,IntegerMatrix
wrapinteger
vectors/matrices inR
;List
wraps alist
inR
;Function
wraps afunction
inR
;- see here for more classes.
The pattern is always the same, upon passing from R
to a C++
function, wrap all the SEXP
inputs via one of the wrapper classes defined in Rcpp
, so that you will be able manipulate them in C++
without thinking about R
memory management. As we will see in the next section, the conversion from SEXP
to an Rcpp
wrapper can be handled automatically by Rcpp
.
Rcpp via sourceCpp()
Consider the following C++
function:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export(name = "expSmoothRcpp")]]
NumericVector expSmoothRcpp_I(const NumericVector y, const double a)
{
int ni = y.size();
NumericVector ys(ni);
ys[0] = y[0];
for(int i = 1; i < ni; i++){
ys[i] = a * ys[i-1] + (1 - a) * y[i];
}
return ys;
}
This is very similar to expSmoothRcpp_manual
(see above). In fact, if we leave aside the comment:
// [[Rcpp::export(name = "expSmoothRcpp")]]
for a moment, the main difference is that now the function signature has changed from:
RcppExport SEXP expSmoothRcpp_manual(SEXP ySEXP, SEXP aSEXP)
to:
NumericVector expSmoothRcpp_I(const NumericVector y, const double a)
Hence, the function is not using RcppExport
and its inputs and output are not of class SEXP
, which means that this function cannot be directly called from R
using .Call()
. To make this function accessible from R
, we can do:
sourceCpp("expSmoothRcpp_I.cpp")
which compiles expSmoothRcpp_I
, loads the corresponding dynamic library in R
(using dyn.load
) and creates an R
wrapper called expSmoothRcpp
. Hence, to test if this worked we do:
max( abs(expSmoothRcpp(y, 0.9) - .Call("expSmooth2", y, 0.9)) )
## [1] 0
Perfect agreement.
Now, to understand how this works, note that:
expSmoothRcpp
## function (y, a)
## .Call(<pointer: 0x7f80bfb018f0>, y, a)
shows that expSmoothRcpp
is a simple wrapper around a .Call
to a C++ function called <pointer: 0x7ff6985a0520>
(or similar). The R function and the C++ function called in the internal call to .Call
have been generated by the call to sourceCpp
. We can get more details about what sourceCpp
is doing, by using the verbose
argument:
sourceCpp("expSmoothRcpp_I.cpp", rebuild = TRUE, verbose = TRUE)
##
## Generated extern "C" functions
## --------------------------------------------------------
##
##
## #include <Rcpp.h>
## #ifdef RCPP_USE_GLOBAL_ROSTREAM
## Rcpp::Rostream<true>& Rcpp::Rcout = Rcpp::Rcpp_cout_get();
## Rcpp::Rostream<false>& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get();
## #endif
##
## // expSmoothRcpp_I
## NumericVector expSmoothRcpp_I(const NumericVector y, const double a);
## RcppExport SEXP sourceCpp_1_expSmoothRcpp_I(SEXP ySEXP, SEXP aSEXP) {
## BEGIN_RCPP
## Rcpp::RObject rcpp_result_gen;
## Rcpp::RNGScope rcpp_rngScope_gen;
## Rcpp::traits::input_parameter< const NumericVector >::type y(ySEXP);
## Rcpp::traits::input_parameter< const double >::type a(aSEXP);
## rcpp_result_gen = Rcpp::wrap(expSmoothRcpp_I(y, a));
## return rcpp_result_gen;
## END_RCPP
## }
##
## Generated R functions
## -------------------------------------------------------
##
## `.sourceCpp_1_DLLInfo` <- dyn.load('/tmp/RtmpNhjugc/sourceCpp-x86_64-pc-linux-gnu-1.0.10/sourcecpp_c42f4bb5317e/sourceCpp_3.so')
##
## expSmoothRcpp <- Rcpp:::sourceCppFunction(function(y, a) {}, FALSE, `.sourceCpp_1_DLLInfo`, 'sourceCpp_1_expSmoothRcpp_I')
##
## rm(`.sourceCpp_1_DLLInfo`)
##
## Building shared library
## --------------------------------------------------------
##
## DIR: /tmp/RtmpNhjugc/sourceCpp-x86_64-pc-linux-gnu-1.0.10/sourcecpp_c42f4bb5317e
##
## /opt/R/4.2.2/lib/R/bin/R CMD SHLIB --preclean -o 'sourceCpp_3.so' 'expSmoothRcpp_I.cpp'
The first part of the text output states that some extern "C"
functions have been generated. In particular, we see the definition of the function:
RcppExport SEXP sourceCpp_1_expSmoothRcpp_I(SEXP ySEXP, SEXP aSEXP)
The fact that the definition of sourceCpp_1_expSmoothRcpp
is preceded by RcppExport
and that the inputs and output are of class SEXP
means that the function can be called from R using .Call
(remember that this is a requirement of .Call
). The fact that sourceCpp_1_expSmoothRcpp
is compatible with .Call
and that it contains a call to expSmoothRcpp_I
(our C++ code, above) shows that sourceCpp_1_expSmoothRcpp
is a wrapper around expSmoothRcpp_I
, making it accessible from R
. We now examine the function body:
Rcpp::RObject rcpp_result_gen;
creates an object of classRObject
, which is defined inRcpp.h
. TheRObject
class is a wrapper around anSEXP
object, which is not copied. Here the object is initially empty. The memory ofRObject
is managed automatically, that is the object is protected from garbage collector while it is in scope. TheRObject
is a base class, from which manyRcpp
classes (such asNumericVector
,List
,Function
, etc) are derived.Rcpp::RNGScope rcpp_rngScope_gen;
has to do with the initialization of the random number generator, which is not important here, but we’ll say more about it later.Rcpp::traits::input_parameter< const NumericVector >::type y(ySEXP);
creates an objecty
, which is a wrapper aroundySEXP
.y
is a constantNumericVector
, hence it can be passed to ourexpSmoothRcpp
function. A similar thing is done fora
, in the next line of code. Explaining howinput_parameter
works is quite involved, but for our purposes this code is equivalent to doingconst NumericVector y = as<const NumericVector>(ySEXP);
as in our original
expSmoothRcpp_manual
function.
So, to make our expSmoothRcpp_I
function accessible from R
, sourceCpp
has written a C++
wrapper which is compatible with .Call
and does all the necessary conversions from SEXP
to NumericVector
, double
and potentially many other Rcpp
or C++
classes for us. The next lines in the text output show that sourceCpp
has:
- dynamically loaded the shared object generated by compiling the
C++
code inexpSmoothRcpp_I.cpp
(see the last line of the text output); - built the
expSmoothRcpp
wrapper function.
The last thing that needs to be explained is the purpose of the comment preceding our C++
function definition, that is:
// [[Rcpp::export(name = "expSmoothRcpp")]]
NumericVector expSmoothRcpp_I(const NumericVector y, const double a)
Here Rcpp::exportRcpp
is an example of the Rcpp export attribute, used to declare that the expSmoothRcpp_I
C++
function should be callable from R via the automatically generated R
and C++
code we have described above. The optional (name = "expSmoothRcpp")
argument is used to specify the name of the R function that should be used to access the C++
code. In fact, simply doing:
// [[Rcpp::export]]
NumericVector expSmoothRcpp_I(const NumericVector y, const double a)
would have created an R
wrapper called expSmoothRcpp_I
. For more on attributes, see the Rcpp vignette on attributes, which also clarifies what are the requirements that must met by a C++
function to be exportable to R
via Rcpp::exportRcpp
.
Inline Rcpp via cppFunction()
and evalCpp()
While it is generally preferable to have C++
code stored in its own .cpp
source files, Rcpp
provides mechanisms for defining and executing C++
functions within an R script. In particular, consider the following standard Rcpp
example:
sourceCpp(code = '
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export(name = "fiboInline1")]]
int fibonacci(const int n) {
if (n < 2)
return n;
else
return (fibonacci(n-1)) + fibonacci(n-2);
}')
which creates a C++
function for computing the \(n\)-th element of the Fibonacci sequence:
fiboInline1(6)
## [1] 8
This is equivalent to having defined the function in, say, fibo.cpp
and having sourced it using sourceCpp("fibo.cpp")
. An even more compact approach involves using the cppFunction
function:
cppFunction('
int fiboInline2(const int x) {
if (x < 2)
return x;
else
return (fiboInline2(x-1)) + fiboInline2(x-2);
}
')
fiboInline2(6)
## [1] 8
Using Rcpp
in an inline fashion can be convenient we dealing with very small chunks of C++
code. Simple C++
expression can be evaluated via evalCpp
, for instance:
evalCpp('1.0 + 1.0')
## [1] 2
As for sourceCpp
, we can use the verbose
argument to look at the code automatically generated by Rcpp
:
evalCpp('1.0 + 1.0', verbose = TRUE)
##
## Generated code for function definition:
## --------------------------------------------------------
##
## #include <Rcpp.h>
##
## using namespace Rcpp;
##
## // [[Rcpp::export]]
## SEXP get_value(){ return wrap( 1.0 + 1.0 ) ; }
##
## No rebuild required (use rebuild = TRUE to force a rebuild)
## [1] 2
Notice the use of Rcpp::wrap
when the get_value
function returns. wrap
allows to transfer objects from C++ to R, for example here it converts the double 2.0 to a numeric vector in R. An important point is that, when converting from R to C++ using Rcpp::as<T>
, we need to specify the C++ type via the T
argument, while the conversion from C++ to R using wrap
is handled automatically.
References
- Allaire, J.J., Eddelbuettel, D. and François, R., 2018. Rcpp Attributes. Vignette included in R package Rcpp, URL http://CRAN. R-Project. org/package= Rcpp.
- Eddelbuettel, D., 2013. Seamless R and C++ integration with Rcpp. New York: Springer.
- Eddelbuettel, D. and Balamuta, J.J., 2018. Extending R with C++: A Brief Introduction to Rcpp. The American Statistician, 72(1), pp.28-36.
- Wickham, H., 2014. Advanced r. Chapman and Hall/CRC.