在C++函数中,Rcpp对象如何传递给其他函数(通过引用或通过复制)?

gve*_*yon 12 performance pointers r rcpp

我刚刚用Rcpp编写了一个新版本的ABCoptim包.大约30倍的加速,我对新版本的性能(与旧版本)非常满意,但我仍然有一些担心,如果我有空间来提高性能而不需要修改太多的代码.

在ABCoptim的主要功能(用C++编写)中,我传递一个包含"蜜蜂位置"(NumericMatrix)的Rcpp :: List对象和一些带有算法本身重要信息的NumericVectors.我的问题是,当我将Rcpp :: List对象传递给其他函数时,例如

#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"]);
}
Run Code Online (Sandbox Code Playgroud)

Rcpp在while循环中做了什么?请问x对象是通过引用或深拷贝的功能传递ABCfun2ABCfun3?我已经看到'const List&x'的用法,它告诉我可以使用指针传递Rcpp对象,但问题是我需要这个列表是可变的(并且没有常量),无论如何要改进它吗?我担心这个x List的迭代拷贝可能会减慢我的代码速度.

PS:我还是C++的新手,而且我正在使用Rcpp来学习C++.

Rom*_*ois 20

除非你提出要求,否则Rcpp中没有深层拷贝clone.传递值时,您正在创建一个新List对象,但它使用相同的底层R对象.

因此,传递值和传递参考之间的差异很小.

但是,当您通过值传递时,您必须为再次保护基础对象付出代价.它可能会产生额外的成本,因为这个Rcpp依赖于递归效率不高R_PreserveObject.

我的指导方针是尽可能通过参考,以便您不需要支付额外的保护价格.如果你知道ABCfun2不会改变对象,我建议通过引用传递给const : ABCfun2( const List& ). 如果您要对其进行更改List,那么我建议您使用ABCfun2( List& ).

考虑以下代码:

#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) ;


}
Run Code Online (Sandbox Code Playgroud)

我所做的就是将一个列表传递给一个函数,更新它并打印一些关于指向底层R对象的指针和指向List对象(this)的指针的信息.

下面是当我打电话会发生什么结果test_copytest_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
Run Code Online (Sandbox Code Playgroud)

我们从与R对象关联的现有列表开始.

           initial:  SEXP=<0x7fda4926d278>. List=0x7fff5bb5efd0
Run Code Online (Sandbox Code Playgroud)

我们按值传递它,fun_copy所以我们得到一个新的List但使用相同的底层R对象:

       in fun_copy:  SEXP=<0x7fda4926d278>. List=0x7fff5bb5ef30
Run Code Online (Sandbox Code Playgroud)

我们退出fun_copy.再次使用相同的底层R对象,并回到原来的List:

after fun_copy (1):  SEXP=<0x7fda4926d278>. List=0x7fff5bb5efd0
Run Code Online (Sandbox Code Playgroud)

现在我们再次调用,fun_copy但这次更新了一个不在列表中的组件:x["d"]="foo".

       in fun_copy:  SEXP=<0x7fda48989120>. List=0x7fff5bb5ef20
Run Code Online (Sandbox Code Playgroud)

List别无选择,只能创建一个新的底层R对象,但这个对象只是本地的底层List.因此,当我们离开时get_copy,我们将恢复原来List的原始底层SEXP.

after fun_copy (2):  SEXP=<0x7fda4926d278>. List=0x7fff5bb5efd0
Run Code Online (Sandbox Code Playgroud)

这里的关键是第一次"a"已经在列表中,所以我们直接更新了数据.因为本地对象fun_copy和外部对象test_copy共享相同的底层R对象,所以内部的修改fun_copy被传播.

第二次,fun_copy增长其本地List对象,将其与SEXP不传播到外部函数的全新对象相关联.

现在考虑通过引用传递时会发生什么:

> 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"
Run Code Online (Sandbox Code Playgroud)

只有一个List对象0x7fff5b909fd0.当我们必须SEXP在第二次调用中获得新内容时,它会正确地传播到外层.

对我来说,通过引用传递的行为更容易理解.


Dir*_*tel 12

简述:

  1. void ABCfun(List x)按值传递,但是再次List是一个Rcpp对象包装一个SEXP指针 - 所以这里的成本低于C++程序员所怀疑的,实际上它是轻量级的.(但正如罗曼正确指出的那样,额外的保护层会有成本.)

  2. void ABCfun(const List x)承诺不会改变x,但再次因为它是一个指针......

  3. void ABCfun(const List & x) 看起来与C++程序员最为正常,并且自去年以来在Rcpp中得到了支持.

事实上,在Rcpp环境中,这三者大致相同.但是你应该按照最好的C++实践来思考并且更喜欢3.因为有一天你可以使用a std::list<....>而不是在这种情况下const引用显然是更可取的(Scott Meyers在Effective C++中有一篇关于这个的完整帖子(或者可能在同伴中)更有效的C++).

但最重要的一课是,您不应该仅仅相信人们在互联网上告诉您的内容,而应该尽可能地衡量和描述.