2016-08-05 65 views
3

使用含有具有C一个.cpp文件中RCPP包的Rcpp.package.skeleton函数I已经创建了一个包++函数返回0:结果不一致内部lapply

#include <Rcpp.h> 

using namespace Rcpp; 

// [[Rcpp::export]] 
RcppExport SEXP just_zero() { 
BEGIN_RCPP 
    Rcpp::RNGScope __rngScope; 
    return wrap(0.0); 
END_RCPP 
} 

当安装并加载包从RI可以通过lapply通过.Call调用函数。正如预期的那样,它(似乎)总是返回0:

> x <- lapply(seq(10000), function(i) { x <- .Call('just_zero'); stopifnot(x == 0); x }) 
#*no errors!* 

但是,显然这是由lapply返回的值包含非零:

> range(simplify2array(x)) 
[1] 0 3 

不幸的是,使用set.seed不使这些返回的值是可重复的,有时我确实得到了[1] 0 0,有时候其他值,例如[1] "0" "TRUE"。另外一个线索是删除线路Rcpp::RNGScope __rngScope;解决了这个问题。

为什么lapply返回的对象中有非零元素(特别是我们检查过.Call返回的值),以及RNGScope如何使用它?

我抄录如下贴在Linux上这种行为和OS X.会话信息从OS X:

> sessionInfo() 
R Under development (unstable) (2016-08-03 r71023) 
Platform: x86_64-apple-darwin13.4.0 (64-bit) 
Running under: OS X Mavericks 10.9.5 

locale: 
[1] en_GB.UTF-8/en_GB.UTF-8/en_GB.UTF-8/C/en_GB.UTF-8/en_GB.UTF-8 

attached base packages: 
[1] stats  graphics grDevices utils  datasets methods base 

other attached packages: 
[1] bug_1.0   devtools_1.12.0 

loaded via a namespace (and not attached): 
[1] tools_3.4.0 withr_1.0.2 memoise_1.0.0 Rcpp_0.12.6 git2r_0.15.0 
[6] digest_0.6.10 
+2

'Rcpp :: wrap()'助手是**不是**模板化的。它采用它的参数,并返回一个“SEXP”。但正如@coatless向您解释的,通过Rcpp属性可以更轻松地实现这一切。 –

+0

谢谢!现在已经在这个问题中得到了反映... – Daniel

回答

4

下面是一个为我一贯重现问题的示例。

#include <Rcpp.h> 
using namespace Rcpp; 

// [[Rcpp::export]] 
void dummy() {} 

extern "C" SEXP just_zero() { 
    Rcpp::RNGScope rngScope; 
    return wrap(0.0); 
} 

/*** R 
n <- 1E5 
x <- unlist(lapply(seq(n), function(i) { 
    .Call('just_zero') 
})) 

unique(x) 
*/ 

调用此Rcpp::sourceCpp()让我如

> unique(x) 
[1]  0 8371 16021 20573 25109 43103 47563 56438 60852 78413 82773 95765 

这是什么原因造成的?要理解这一点,我们需要了解的RNGScope的定义:在这里我们可以看到:

https://github.com/RcppCore/Rcpp/blob/9fbdf78fe225607dc2c8af7267a6840af0aba10e/inst/include/Rcpp/stats/random/random.h#L27-L31

和它使用的方法定义如下:

https://github.com/RcppCore/Rcpp/blob/9fbdf78fe225607dc2c8af7267a6840af0aba10e/src/api.cpp#L63-L75

最重要功能PutRNGState,在此定义为:

https://github.com/wch/r-source/blob/1c88a057594a0348f2bf75514a8015caeedbff93/src/main/RNG.c#L424-L446

现在

,这是有效的,当just_zero被称为会发生什么:创建

  1. RNGScope对象,以引跑与其相关的析构函数。
  2. wrap(0.0)的结果是REALSXP,长度为1,值为0。请注意,此对象是未受保护的,因此符合垃圾回收的条件。
  3. 该函数返回,并调用RNGScope析构函数。
  4. 这调用R例程PutRNGstate(),它本身将调用allocVector,从而触发垃圾回收器。这意味着你想要返回的对象SEXP可以被收集,因此将是垃圾!

因此,总而言之 - 使用Rcpp属性,因为它将为您安全地完成这一切。


要理解为什么RCPP属性使得这个“安全”,看看生成的代码:

// just_zero 
SEXP just_zero(); 
RcppExport SEXP sourceCpp_0_just_zero() { 
BEGIN_RCPP 
    Rcpp::RObject __result; 
    Rcpp::RNGScope __rngScope; 
    __result = Rcpp::wrap(just_zero()); 
    return __result; 
END_RCPP 
} 

注意,输出结果被分配到Rcpp::RObject,它保护的对象,我们保证此对象的构造之前RNGScope对象,它确保它将保持保护,而RNGScope析构函数运行。

+0

很好的解释! @丹尼尔,这是你寻求的答案。 – coatless

+0

感谢您提供非常明确的答案,并让问题变得可重复!我想在一次调用'just_zero'并因此原始问题中的stopifnot(x == 0)'行不会触发任何错误之后,垃圾收集是不太可能的。 – Daniel

+0

不是'stopifnot()'的原始问题意味着垃圾收集/损坏发生_after_返回到R?如果它是用C++收集的垃圾,那么它会回来损坏。 –

3

这不是完全的答案,但因为它去,我会展开。但是,我无法在macOS上重现此行为。

我相信你遇到的问题是由于你使用了RcppExport和Rcpp属性,例如, :

// [[Rcpp::export]] 
RcppExport SEXP just_zero() 

应通过完成:

library("Rcpp") 
cppFunction("double just_zero() { 
    return 0.0; 
}") 

x <- lapply(seq(10000), function(i) { x <- just_zero(); stopifnot(x == 0); x }) 

all(range(simplify2array(x)) == 0) 

或将以下为file.cpp

#include <Rcpp.h> 
using namespace Rcpp; 

// [[Rcpp::export]] 
double just_zero() { 
    return 0.0; 
} 

/*** R 
x <- lapply(seq(10000), function(i) { x <- just_zero(); stopifnot(x == 0); x }) 
all(range(simplify2array(x)) == 0) 
*/ 

如果打算使用sourceCpp()

+0

感谢您的回答 - 在问题的cpp代码上使用sourceCpp修复了问题(通过verbose = TRUE表明它稍微扩展了代码以便将wrap的值赋值给变量之前返回 - 确实使用由sourceCpp打印的代码来安装软件包也会修复问题)。删除RcppAttributes并没有帮助,不幸的是 - 大概在安装软件包时不会使用它... – Daniel