在 lapply 中使用 .Call 在 R 中产生不一致的结果
Inconsistent results in R using .Call inside lapply
我使用 Rcpp 包的 Rcpp.package.skeleton
函数创建了一个包,其中包含一个带有 C++ 函数的 .cpp 文件 returning 0:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
RcppExport SEXP just_zero() {
BEGIN_RCPP
Rcpp::RNGScope __rngScope;
return wrap(0.0);
END_RCPP
}
当我从 RI 安装和加载包时,我可以使用 .Call via lapply 调用该函数。正如预期的那样,它(似乎)总是 return 0:
> x <- lapply(seq(10000), function(i) { x <- .Call('just_zero'); stopifnot(x == 0); x } )
#*no errors!*
但是,显然 return 由 lapply
编辑的值包含非零值:
> range(simplify2array(x))
[1] 0 3
不幸的是,使用 set.seed
并不能使这些 returned 值可重现,有时我确实得到 [1] 0 0
,有时其他值,例如[1] "0" "TRUE"
。另一个线索是删除行 Rcpp::RNGScope __rngScope;
可以解决问题。
为什么 return 由 lapply
编辑的对象中有非零元素(特别是当我们检查了由 .Call
编辑的值 return 时),为什么使用 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
这不是一个完整的答案,但我会逐步扩展它。但是,我无法在 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()
。
这是一个为我始终如一地重现问题的示例。
#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/src/api.cpp#L63-L75
具有最重要的功能,PutRNGState
,在此处定义:
现在,这实际上是调用 just_zero
时发生的情况:
RNGScope
对象已创建,其关联的析构函数已准备好 [=71=]。
- 创建了
wrap(0.0)
的结果,即长度为 1 且值为 0 的 REALSXP
。请注意,此对象 未受保护 因此符合垃圾回收条件。
- 函数returns,调用了
RNGScope
析构函数。
- 这将调用 R 例程
PutRNGstate()
,后者本身将调用 allocVector
,从而触发垃圾收集器。这意味着您想要 return 的 SEXP
对象可以被收集,因此将是垃圾!
所以,总而言之——使用 Rcpp 属性,因为它会为您安全地完成这一切。
要了解为什么 Rcpp 属性会这样 'safe',请查看生成的代码:
// 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
析构函数为 运行.
时保持受保护
我使用 Rcpp 包的 Rcpp.package.skeleton
函数创建了一个包,其中包含一个带有 C++ 函数的 .cpp 文件 returning 0:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
RcppExport SEXP just_zero() {
BEGIN_RCPP
Rcpp::RNGScope __rngScope;
return wrap(0.0);
END_RCPP
}
当我从 RI 安装和加载包时,我可以使用 .Call via lapply 调用该函数。正如预期的那样,它(似乎)总是 return 0:
> x <- lapply(seq(10000), function(i) { x <- .Call('just_zero'); stopifnot(x == 0); x } )
#*no errors!*
但是,显然 return 由 lapply
编辑的值包含非零值:
> range(simplify2array(x))
[1] 0 3
不幸的是,使用 set.seed
并不能使这些 returned 值可重现,有时我确实得到 [1] 0 0
,有时其他值,例如[1] "0" "TRUE"
。另一个线索是删除行 Rcpp::RNGScope __rngScope;
可以解决问题。
为什么 return 由 lapply
编辑的对象中有非零元素(特别是当我们检查了由 .Call
编辑的值 return 时),为什么使用 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
这不是一个完整的答案,但我会逐步扩展它。但是,我无法在 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()
。
这是一个为我始终如一地重现问题的示例。
#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/src/api.cpp#L63-L75
具有最重要的功能,PutRNGState
,在此处定义:
现在,这实际上是调用 just_zero
时发生的情况:
RNGScope
对象已创建,其关联的析构函数已准备好 [=71=]。- 创建了
wrap(0.0)
的结果,即长度为 1 且值为 0 的REALSXP
。请注意,此对象 未受保护 因此符合垃圾回收条件。 - 函数returns,调用了
RNGScope
析构函数。 - 这将调用 R 例程
PutRNGstate()
,后者本身将调用allocVector
,从而触发垃圾收集器。这意味着您想要 return 的SEXP
对象可以被收集,因此将是垃圾!
所以,总而言之——使用 Rcpp 属性,因为它会为您安全地完成这一切。
要了解为什么 Rcpp 属性会这样 'safe',请查看生成的代码:
// 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
析构函数为 运行.