Within C ++ functions, how are Rcpp objects passed to other functions (by reference or by copy)?

I just finished writing a new version of the ABCoptim package using Rcpp. With about 30x speedup, I am very pleased with the performance of the new version (compared to the old version), but I still have some problems if I have room to improve performance without changing too much code.

As part of the main ABCoptim function (written in C ++), I pass an Rcpp :: List object containing the "positions of the bees" (NumericMatrix) and some numerical vectors with important information for the algorithm itself. My question is when I pass the Rcpp :: List object around other functions like

#include <Rcpp.h> using namespace Rcpp; List ABCinit([some input]){[some code here]}; void ABCfun2(List x){[some code here]}; void ABCfun3(List x){[some code here]}; List ABCmain([some input]) { List x = ABCinit([some input]); while ([some statement]) { ABCfun2(x); ABCfun3(x); } ... return List::create(x["results"]); } 

What does Rcpp do in a while loop? ABCfun2 object x passed by reference or by deep copy in the functions ABCfun2 and ABCfun3 ? I saw the use of "const List & x", which tells me that I can pass Rcpp objects with pointers, but the fact is that I need this list as a variable (and not a constant), anyway, to improve this? I am afraid that an iterative copy of this x-list might slow down my code.

PS: I am still new to C ++, in addition, I use Rcpp to learn C ++.

+6
source share
2 answers

There is no deep copy in Rcpp unless you request it with clone . When you pass in the value, you create a new List object, but it uses the same base object R.

Thus, there are different values ​​between passing by value and passing by reference.

However, when you pass by value, you have to pay the price for protecting the main object again. This may incur an extra charge, since this Rcpp relies on a recursive, not very efficient R_PreserveObject .

My guide will follow the link whenever possible so that you do not pay extra protection. If you know that ABCfun2 will not change the object, I would advise you to pass a link to const: ABCfun2( const List& ) . If you are going to make changes to the List , I would recommend using ABCfun2( List& ) .

Consider this code:

 #include <Rcpp.h> using namespace Rcpp ; #define DBG(MSG,X) Rprintf("%20s SEXP=<%p>. List=%p\n", MSG, (SEXP)X, &X ) ; void fun_copy( List x, const char* idx ){ x[idx] = "foo" ; DBG( "in fun_copy: ", x) ; } void fun_ref( List& x, const char* idx ){ x[idx] = "bar" ; DBG( "in fun_ref: ", x) ; } // [[Rcpp::export]] void test_copy(){ // create a list of 3 components List data = List::create( _["a"] = 1, _["b"] = 2 ) ; DBG( "initial: ", data) ; fun_copy( data, "a") ; DBG( "\nafter fun_copy (1): ", data) ; // alter the 1st component of ths list, passed by value fun_copy( data, "d") ; DBG( "\nafter fun_copy (2): ", data) ; } // [[Rcpp::export]] void test_ref(){ // create a list of 3 components List data = List::create( _["a"] = 1, _["b"] = 2 ) ; DBG( "initial: ", data) ; fun_ref( data, "a") ; DBG( "\nafter fun_ref (1): ", data) ; // alter the 1st component of ths list, passed by value fun_ref( data, "d") ; DBG( "\nafter fun_ref (2): ", data) ; } 

All I do is pass a list of the function, update it and print some information about the pointer to the main R object and the pointer to the List ( this ) object.

Here are the results of what happens when I call test_copy and test_ref :

 > test_copy() initial: SEXP=<0x7ff97c26c278>. List=0x7fff5b909fd0 in fun_copy: SEXP=<0x7ff97c26c278>. List=0x7fff5b909f30 after fun_copy (1): SEXP=<0x7ff97c26c278>. List=0x7fff5b909fd0 $a [1] "foo" $b [1] 2 in fun_copy: SEXP=<0x7ff97b2b3ed8>. List=0x7fff5b909f20 after fun_copy (2): SEXP=<0x7ff97c26c278>. List=0x7fff5b909fd0 $a [1] "foo" $b [1] 2 

Let's start with the existing list associated with object R.

  initial: SEXP=<0x7fda4926d278>. List=0x7fff5bb5efd0 

We pass it by the value of fun_copy , so we get a new List , but using the same basic R object:

  in fun_copy: SEXP=<0x7fda4926d278>. List=0x7fff5bb5ef30 

Exit fun_copy . the same main R object back and forth to the original List :

 after fun_copy (1): SEXP=<0x7fda4926d278>. List=0x7fff5bb5efd0 

Now we call fun_copy again, but this time we are updating a component that was not in the list: x["d"]="foo" .

  in fun_copy: SEXP=<0x7fda48989120>. List=0x7fff5bb5ef20 

List had no choice but to create a new basic R object, but this object lies only in the local List . Therefore, when we exit get_copy , we return to the original List with its original SEXP subtext.

 after fun_copy (2): SEXP=<0x7fda4926d278>. List=0x7fff5bb5efd0 

The main thing is that the first time "a" already on the list, so we updated the data directly. Since the local fun_copy object and the external object from test_copy have the same base object R, modifications were common inside fun_copy .

The second time, fun_copy grows its local List object, associating it with a new SEXP that does not extend to an external function.

Now consider what happens when you follow the link:

 > test_ref() initial: SEXP=<0x7ff97c0e0f80>. List=0x7fff5b909fd0 in fun_ref: SEXP=<0x7ff97c0e0f80>. List=0x7fff5b909fd0 after fun_ref(1): SEXP=<0x7ff97c0e0f80>. List=0x7fff5b909fd0 $a [1] "bar" $b [1] 2 in fun_ref: SEXP=<0x7ff97b5254c8>. List=0x7fff5b909fd0 after fun_ref(2): SEXP=<0x7ff97b5254c8>. List=0x7fff5b909fd0 $a [1] "bar" $b [1] 2 $d [1] "bar" 

There is only one List object 0x7fff5b909fd0 . When we need to get the new SEXP in the second call, it will correctly extend to the external level.

For me, the behavior that you get when passing through links is much easier to reason about.

+11
source

Short:

  • void ABCfun(List x) passes by value, and then again List is an Rcpp object that wraps SEXP , which is a pointer, so the cost here is less than the intended C ++ programmer, and it is in fact easy. (But, as Romain rightly notes, there is value in the extra layer of defense.)

  • void ABCfun(const List x) promises do not change x , but again because it is a pointer ...

  • void ABCfun(const List & x) looks the most common for a C ++ programmer and has been supported in Rcpp since last year.

Ipso facto, in the context of Rcpp, all three are about the same. But you should consider the principles of best practice in C ++ and prefer 3. Like one day, you can use std::list<....> instead, in which case a reference to a constant is clearly preferable (Scott Meyers has the whole entry more about this in Effective C ++ (or perhaps in the companion More Effective C ++).

But the most important lesson is that you should not just believe what people are telling you on the Internet, but rather measure your profile when possible.

+10
source

Source: https://habr.com/ru/post/970552/


All Articles