Skip to content

Fast way to call an R function from C++?

6 messages · Jiefei Wang, Kevin Ushey, Iñaki Ucar

#
Hi,

I'm looking for a most efficient way to call an R function from C++ in a
package. I know there are two functions (`R_forceAndCall` and `Rf_eval`)
that can do the "call" part, but both are slow compared to calling the same
function in R. I also try to use Rcpp and it is the worse one. Here is my
test code:

C++ code:
```
// [[Rcpp::export]]
SEXP C_test1(SEXP f, SEXP x) {
SEXP call =PROTECT(Rf_lang2(f, x));
SEXP val = R_forceAndCall(call, 1, R_GlobalEnv);
UNPROTECT(1);
return val;
}

// [[Rcpp::export]]
SEXP C_test2(SEXP expr, SEXP env) {
SEXP val = Rf_eval(expr, env);
return val;
}

// [[Rcpp::export]]
SEXP C_test3(SEXP f,SEXP x) {
Function fun(f);
return fun(x);
}
```

R code:
```
testFunc<-function(x){
  x=x^2
  return(x)
}
evn=new.env()
evn$x=x
expr=quote(testFunc(evn$x))

testFunc(evn$x)
C_test1(testFunc, evn$x)
C_test2(expr,evn)
C_test3(testFunc,evn$x)
```

For the results, I run each function 1,000,000 times:

   - testFunc : 0.47 sec
   - C_test1 : 2.46 sec
   - C_test2 : 2.74 sec
   - C_test3 : 18.86 sec

It is clear to see that calling an R function in R is the fast one, it is
about 5X faster than ` R_forceAndCall ` and ` Rf_eval`. the latter two
functions have a similar performance and using Rcpp is the worst one. Is it
expected? Why is calling an R function from C++ much slower than calling
the function from R? Is there any faster way to do the function call in C++?

Best,
Jiefei
#
Hi Jiefei,

Calling into R from C++ code is more complicated than one might think.
Please see Tomas Kalibera's post here:
https://developer.r-project.org/Blog/public/2019/03/28/use-of-c---in-packages/index.html

The Rcpp Function class is more expensive than a regular Rf_eval()
because it tries to prevent errors (longjmps) from jumping to the top
level, so that the C++ stack can be properly unwound. In addition,
your example also pays a cost for the indirect function calls used
with Rcpp attributes, since the generated R wrapper functions will
then use `.Call()` under the hood. You could also try to directly use
.Call() to avoid that extra R function call overhead.

Even then, R's dispatch system for primitive functions (like `^`) is
likely still going to be faster than what you get from the .Call()
interface, especially since some extra validation does occur when
using .Call(). In practice, that difference is usually negligible
outside of synthetic benchmarks.

Best,
Kevin
On Tue, Jun 18, 2019 at 10:41 AM King Jiefei <szwjf08 at gmail.com> wrote:
#
On Tue, 18 Jun 2019 at 19:41, King Jiefei <szwjf08 at gmail.com> wrote:
Yes, there is: enable fast evaluation by setting
-DRCPP_USE_UNWIND_PROTECT, or alternatively, use

// [[Rcpp::plugins(unwindProtect)]]

I?aki
#
For reference, your benchmark using UNWIND_PROTECT:
user  system elapsed
  0.331   0.000   0.331
user  system elapsed
  2.029   0.000   2.036
user  system elapsed
  2.307   0.000   2.313
user  system elapsed
  2.131   0.000   2.138

I?aki
On Tue, 18 Jun 2019 at 20:35, I?aki Ucar <iucar at fedoraproject.org> wrote:

  
    
#
Hello Kevin and I?aki,

Thanks for your quick responses. I sincerely appreciate them! I can see how
complicated it is to interact with R in C. I?aki's suggestion is very
helpful, I saw there is a lot of performance gain by turning the flag on,
but sadly the best performance it can offer still cannot beat R itself. It
is interesting to see that C++ is worse than R in this special case despite
there is a common belief that C++ code is the fast one... Anyway, thanks
again for your suggestions and reference!

Best,
Jiefei
On Tue, Jun 18, 2019 at 2:39 PM I?aki Ucar <iucar at fedoraproject.org> wrote:

            

  
  
#
On Wed, 19 Jun 2019 at 07:42, King Jiefei <szwjf08 at gmail.com> wrote:
That is misleading. C++ code is faster, that's beyond doubt. But you
are not running C++ code here, you are running R code. So what is
faster, running R code or executing something that then runs R code?
Obviously the first thing is the baseline, and from there, it can only
get worse as you add more layers on top of it.

I?aki