Skip to content

[Rcpp-devel] Alternative way of calling R functions within C++

7 messages · Dirk Eddelbuettel, Kevin Ushey, George Vega Yon

#
Hey there,

Looking at some old R code that I have I found this C++ function that
allows evaluating R-written functions within C++ using Rcpp. While this is
no news, the neat thing of it is that it seems to be faster than
Rcpp::Function. Using microbenchmark I compared using Rcpp::function vs my
implementation vs calling the function from R itself and this is what I got

Unit: relative
                expr min  lq     mean median  uq max neval
  cppFuncall(x, fun) 1.3 1.3 1.39    1.3 1.4  83 10000
 RcppFuncall(x, fun) 7.2 7.1 7.13    6.9 6.8  89 10000
              fun(x) 1.0 1.0 1.00    1.0 1.0   1 10000

So, on average, while Rcpp::Function took ~7 times the R call took, my
implementation took ~1.3 times. To be sure I was not breaking anything I
ran the example using valgrind and there is no memory leak. The source code
for the test follows:

-------- example_calling_r_functions.cpp ----

#include <Rinternals.h>
#include <Rcpp.h>

// [[Rcpp::export]]
SEXP cppFuncall(SEXP par, SEXP fn)
{
  SEXP R_fcall, ans;

  if(!isFunction(fn)) error("'fn' must be a function");
  R_fcall = PROTECT(lang2(fn, R_NilValue));

  SETCADR(R_fcall, par);
  ans=eval(R_fcall, R_GlobalEnv);
  UNPROTECT(1);
  return ans;
}

using namespace Rcpp;

// [[Rcpp::export]]
SEXP RcppFuncall(NumericVector par, Function fn)
{
  return fn(par);
}



/*** R
# R function to be called
fun <- function(x) {
  -cos(x[1])*cos(x[2])*exp(-((x[1] - pi)^2 + (x[2] - pi)^2))
}

# Input data
set.seed(3331)
x <- runif(1e3)

# Benchmarking
library(microbenchmark)
microbenchmark(
  cppFuncall(x, fun), RcppFuncall(x,fun), fun(x), times=1e4,
  unit="relative", control = list(warmup=100)
)
*/

-------- example_calling_r_functions.cpp ----

I've asked around about how to make things faster for function calls in
Rcpp but, from what I've been told it is difficult since the implementation
of Rcpp::Function actually has to go back to R to work (or something like
that :P). Now, this implementation, -cppFuncall-, has no problem when it
comes to passing wrong arguments, e.g. if you pass a character vector to it
R will complain but there won't be any system crash. One big difference is
that here I'm relying on passing all the function's arguments in a single
object while Rcpp::Function does not. Either way, if this is OK this could
be a nice extra feature for Rcpp, I'm thinking of optimization routines (or
other kinds of algorithms) that rely on calling R functions multiple times.

The thing is that I'm still learning C++  and  I'm not Rinternals expert at
all! So I would love to get some feedback from you guys. Does this
function, -cppFuncall-, looks OK? in other words, am I doing/getting
something wrong here?

Thanks,

George G. Vega Yon
+1 (626) 381 8171
http://www.its.caltech.edu/~gvegayon/
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.r-forge.r-project.org/pipermail/rcpp-devel/attachments/20160803/d8814ddc/attachment.html>
#
The problem with your implementation is what happens if R throws an
error. The R longjmp will cause any C++ objects on the stack to leak,
their destructors not to run, and you'll essentially be in a bad place
if you evaluate any R code that might return an error. (For example,
suppose you had a file handle open, and then evaluated your R function
using 'eval', but an R error was produced. That file handle would
'leak' and you wouldn't be able to recover it)

You can instead use the `Rf_tryEval` or `Rf_tryEvalSilent` routines,
but those don't respect active handlers (e.g. warning, message
handlers) and so aren't sufficient for general use.

Rcpp basically handles this by enclosing any expression to be
evaluated in an R 'tryCatch' call, with error + interrupt handlers
attached. This is, of course, slower since a lot more R code is being
evaluated, but right now it's the only way to safely execute an R
function in a C++ context while respecting all other active handlers.

In short, the Rcpp::Function implementation is designed to be as safe
+ correct as possible, with the downside being that it's slower.
However, we generally advise that you shouldn't call back to R too
frequently from a C++ context, so that overhead should in most cases
be not too bad.

Best,
Kevin
On Wed, Aug 3, 2016 at 10:50 AM, George Vega Yon <g.vegayon at gmail.com> wrote:
#
Also note that you definitely don't want to call `Rf_error` from a C++
context in general, as it will bypass any active C++ try-catch blocks,
bypass destructors, and so on. You should call `Rcpp::stop` explicitly
and use Rcpp attributes to ensure the try-catch block is automagically
set up for you.
On Wed, Aug 3, 2016 at 11:28 AM, Kevin Ushey <kevinushey at gmail.com> wrote:
#
Thanks for the quick reply! What kind of errors are we talking about? I a
new run I explicitly caused an error by passing a character vector, and had
no memory leak (using Valgrind):

cppFuncall(letters, fun)
Error in cos(x[1]) : non-numeric argument to mathematical function

If its not too much to ask, could you give an explicit example in which
that happens (memory leak)? Just trying to learn here!

Thanks,


George G. Vega Yon
+1 (626) 381 8171
http://www.its.caltech.edu/~gvegayon/
On Wed, Aug 3, 2016 at 11:29 AM, Kevin Ushey <kevinushey at gmail.com> wrote:

            
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.r-forge.r-project.org/pipermail/rcpp-devel/attachments/20160803/45c11724/attachment-0001.html>
#
On 3 August 2016 at 11:38, George Vega Yon wrote:
| Thanks for the quick reply! What kind of errors are we talking about? I a new
| run I explicitly caused an error by passing a character vector, and had no
| memory leak (using Valgrind):
| 
| cppFuncall(letters, fun)
| Error in cos(x[1]) : non-numeric argument to mathematical function
| 
| If its not too much to ask, could you give an explicit example in which that
| happens (memory leak)? Just trying to learn here!

You are misreading what Kevin said.  You short ten-line example runs fine.
We are not saying it has an error.

What Kevin explained to you is that in the context of larger programs,
possibly with inputs you don't know yet, some errors may occur. And both
Rcpp:Function() and Rcpp::stop() can recover from that.

Your example cannot.  So by all means use it as a small (local) script if the
few milliseconds matter to you.  But think twice about using it in a larger
context, or about promoting it as a general solution, and understand why we
can't put it into Rcpp as is.

Hth, Dirk
#
The simplest demonstrating example I can think of:

---

#include <Rcpp.h>
using namespace Rcpp;

struct A { ~A() { Rprintf("~A()"); } };

// [[Rcpp::export]]
void ouch() {
  A a;
  Rf_error("ouch!");
}

/*** R
ouch()
*/

---

Call 'Rcpp::sourceCpp()' on that and you'll see:
Error in ouch() : ouch!

Note that the destructor was not called. Replace `Rf_error` with
`Rcpp::stop` and you will see the destructor is called.

It's possible that you won't have a memory leak per-se (if the memory
is all allocated on the stack, maybe the runtime still knows to just
clear the entire stack after something like this) but not running
destructors is definitely a big problem.

Cheers,
Kevin
On Wed, Aug 3, 2016 at 12:22 PM, Dirk Eddelbuettel <edd at debian.org> wrote:
#
Dirk, good point. This is a very simple function and if I add complexity at
the end there's no significant difference between calling -fun-,
-cppFuncall- and -RcppFuncall-. Just to make this thread more complete, if
I modify -fun- from

fun <- function(x) {
  -cos(x[1])*cos(x[2])*exp(-((x[1] - pi)^2 + (x[2] - pi)^2))
}

to

fun <- function(x) {
  w<-sapply(1:1e3,function(x) -cos(x[1])*cos(x[2])*exp(-((x[1] - pi)^2 +
(x[2] - pi)^2)))
  1
}

Running the benchmark I get

Unit: relative
                expr min lq     mean median uq max neval
  cppFuncall(x, fun)   1  1 1.044215      1  1 6.1  1000
 RcppFuncall(x, fun)   1  1 1.018957      1  1 2.7  1000
              fun(x)   1  1 1.000000      1  1 1.0  1000

Which is way more reasonable from what I was getting at first. No
significant difference overall.

Kevin, thanks for the example, now I get why isn't a good idea! This has
been very useful to me.

Best,

George G. Vega Yon
+1 (626) 381 8171
http://www.its.caltech.edu/~gvegayon/
On Wed, Aug 3, 2016 at 12:34 PM, Kevin Ushey <kevinushey at gmail.com> wrote:

            
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.r-forge.r-project.org/pipermail/rcpp-devel/attachments/20160803/c9144d9a/attachment.html>