2016-08-05 63 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