Reputation: 41
I've created a package using the Rcpp package's Rcpp.package.skeleton
function containing one .cpp file with a C++ function 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
}
When I install and load the package from R I can call the function with .Call via lapply. As expected, it (appears to) always return 0:
> x <- lapply(seq(10000), function(i) { x <- .Call('just_zero'); stopifnot(x == 0); x } )
#*no errors!*
However, apparently the value which is returned by lapply
contains non-zeros:
> range(simplify2array(x))
[1] 0 3
Unfortunately, using set.seed
doesn't make these returned values reproducible, and sometimes I do get [1] 0 0
, sometimes other values, e.g. [1] "0" "TRUE"
. An additional clue is that removing the line Rcpp::RNGScope __rngScope;
fixes the problem.
Why are there non-zero elements in the object returned by lapply
(particularly as we've checked the values returned by .Call
), and how come using RNGScope
is causing it?
I've reproduced this behaviour on Linux and OS X. Session info from OS X pasted below:
> 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
Upvotes: 3
Views: 233
Reputation: 21315
Here's an example that reproduces the issue consistently for me.
#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)
*/
Calling Rcpp::sourceCpp()
on this gives me e.g.
> unique(x)
[1] 0 8371 16021 20573 25109 43103 47563 56438 60852 78413 82773 95765
What's causing this issue? To understand this, we need to understand the definition of RNGScope
: we can see it here:
and the methods it uses are defined here:
https://github.com/RcppCore/Rcpp/blob/9fbdf78fe225607dc2c8af7267a6840af0aba10e/src/api.cpp#L63-L75
with the most important function, PutRNGState
, being defined here:
Now, this is effectively what happens when just_zero
is called:
RNGScope
object is created, with its associated destructor primed to run.wrap(0.0)
, being a REALSXP
with length 1 and value 0, is created. Note that this object is unprotected and so is eligible for garbage collection.RNGScope
destructor is called.PutRNGstate()
, which itself will call allocVector
, thereby triggering the garbage collector. This implies the SEXP
object you wanted to return can be collected, and hence will be garbage!So, in sum -- use Rcpp attributes, as it will take care of doing this all safely for you.
To understand why Rcpp attributes makes this 'safe', look at the generated code:
// 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
}
Note that the output result is assigned to Rcpp::RObject
, which protects the object, and we ensure that this object is constructed before the RNGScope
object, which ensures it will remain protected while the RNGScope
destructor is run.
Upvotes: 5
Reputation: 20746
This is not fully an answer, but I'll expand it as it goes. However, I'm not able to reproduce this behavior on macOS.
I believe the issue you are encountering is due to your use of the RcppExport alongside Rcpp Attributes, e.g. :
// [[Rcpp::export]]
RcppExport SEXP just_zero()
Should be done via:
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)
or place the following into 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)
*/
If the intent is to use sourceCpp()
.
Upvotes: 3