[Rd] reference classes, LAZY_DUPLICATE_OK, and external pointers
Ben Bolker
bbolker at gmail.com
Mon Mar 3 02:42:30 CET 2014
On 14-03-02 08:05 PM, Simon Urbanek wrote:
> Ben,
>
> On Mar 2, 2014, at 7:38 PM, Ben Bolker <bbolker at gmail.com> wrote:
>
>> We (the lme4 authors) are having a problem with doing a proper
>> deep copy of a reference class object in recent versions of R-devel
>> with the LAZY_DUPLICATE_OK flag in src/main/bind.c enabled.
>>
>> Apologies in advance for any improper terminology.
>>
>> TL;DR Is there an elegant way to force non-lazy/deep copying in
>> our case? Is anyone else using reference classes with a field that
>> is an external pointer?
>>
>> This is how copying of reference classes works in a normal
>> situation:
>>
>> library(data.table) ## for address() function
>> setRefClass("defaultRC",fields="theta") d1 <- new("defaultRC")
>> d1$theta <- 1 address(d1$theta) ## "0xbbbbb70" d2 <- d1$copy()
>> address(d2$theta) ## same as above d2$theta <- 2 address(d2$theta)
>> ## now modified, by magic d1$theta ## unmodified
>>
>> The extra complication in our case is that many of the objects
>> within our reference class are actually accessed via an external
>> pointer, which is initialized when necessary -- details are copied
>> below for those who want them, or you can see the code at
>> https://github.com/lme4/lme4
>>
>> The problem is that this sneaky way of copying the object's
>> contents doesn't trigger R's (new) rules for recognizing that a
>> non-lazy copy should be made.
>>
>
> This is not R's decision - AFAICS your code is incorrectly assuming
> that there is no other reference where there is no such guarantee.
> Your code that assigns into the external pointer has to make that
> decision - it's not R's to make since you are taking the full
> responsibility for external pointers by circumventing R's handing.
> External pointers had always had reference semantics. Note that this
> is not new - you had to inspect the NAMED bits and call duplicate()
> yourself to guarantee a copy even in previous R versions. It just so
> happened that bugs of not doing so were often masked by R being more
> conservative such that in some circumstanced there were enough
> references to function arguments that R would defensively create a
> new copy.
>
> So, the same applies as it did before - if you store something that
> you want to be mutable in C/C++ you have to check the references and
> call duplicate() if you don't own the only reference.
>
> Cheers, Simon
Thanks, that's extremely useful.
Ben
>
>
>
>> library(lme4) fm1 <- lmer(Reaction ~ Days + (Days|Subject),
>> sleepstudy) pp <- fm1 at pp pp$theta ## [1] 0.96673279 0.01516906
>> 0.23090960 address(pp$theta) ## something pp$Ptr ## <pointer: ...>
>> xpp <- pp$copy() ## default is deep copy xpp$Ptr ## <pointer:
>> (nil)> address(xpp$theta) ## same as above xpp$setTheta(c(0,0,0))
>> ## referenced through Ptr field xpp$Ptr ## now set to non-nil
>> fm1 at pp$theta ## changes to (0,0,0). oops.
>>
>> So apparently when the xpp$theta object is copied into the
>> external pointer, a reference/lazy copy is made. (xpp$theta
>> itself is read-only, so I can't do the assignment that way)
>>
>> I can hack around this in a very ugly way by doing a trivial
>> modification when assigning inside the copy method:
>>
>> assign("theta",get("theta",envir=selfEnv)+0, envir=vEnv)
>>
>> ... but (a) this is very ugly and (b) it seems very unsafe -- as R
>> gets smarter it should start to recognize trivial changes like x+0
>> and x*1 and *not* copy in these cases ...
>>
>> Method details:
>>
>> ## from R/AllClass.R, merPredD RC definition
>>
>> ptr = function() { 'returns the external pointer,
>> regenerating if necessary' if (length(theta)) { if
>> (.Call(isNullExtPtr, Ptr)) initializePtr() } Ptr },
>>
>> ## ditto
>>
>> initializePtr = function() { Ptr <<- .Call(merPredDCreate, as(X,
>> "matrix"), Lambdat, LamtUt, Lind, RZX, Ut, Utr, V, VtV, Vtr, Xwts,
>> Zt, beta0, delb, delu, theta, u0) ... }
>>
>> merPredDCreate in turn just copies the relevant bits into a new
>> C++ class object:
>>
>> /* see src/external.cpp */
>>
>> SEXP merPredDCreate(SEXP Xs, SEXP Lambdat, SEXP LamtUt, SEXP Lind,
>> SEXP RZX, SEXP Ut, SEXP Utr, SEXP V, SEXP VtV, SEXP Vtr, SEXP Xwts,
>> SEXP Zt, SEXP beta0, SEXP delb, SEXP delu, SEXP theta, SEXP u0) {
>> BEGIN_RCPP; merPredD *ans = new merPredD(Xs, Lambdat, LamtUt, Lind,
>> RZX, Ut, Utr, V, VtV, Vtr, Xwts, Zt, beta0, delb, delu, theta,
>> u0); return wrap(XPtr<merPredD>(ans, true)); END_RCPP; }
>>
>> ______________________________________________
>> R-devel at r-project.org mailing list
>> https://stat.ethz.ch/mailman/listinfo/r-devel
>>
>
More information about the R-devel
mailing list