the following code
require(R6)
Array <- R6Class(
"Array",
public=list(
x=matrix(0,0,0),
initialize=function(a,b,c){
self$x <- matrix(a,b,c)
},
assign=function(z){
self$x[1,1] <- z
invisible(self)
}
)
)
x <- Array$new(0,10,10)
tracemem(x$x)
x$assign(1)
y <- matrix(0,10,10)
tracemem(y)
y[1,1] <- 1
yields this output
> [1] "<0x55ae7be40040>"
> tracemem[0x55ae7be40040 -> 0x55ae7b403700]: <Anonymous>
> > [1] "<0x55ae7b254c90>"
> >
which implies that when the R6 member array is updated a copy of the array is made, whereas when the plain array is updated, the element is update "in-place" (i.e. no copy is generated).
Any idea how I can achieve this for the R6 object?
This happens because of the semantics for subset assignment in R. Subset assignment is the
$<-operator (as inx$y <- 1) or the[<-operator (as inx[y] <- 1).In R, when you do something like
self$x <- y, that actually gets turned into something like this:This creates
*tmp*, which initially points to the same object in memory asx. However, when the assignment toxhappens in the second line, R makes a copy of the object and modifies it. This copy of the object needs to be GC'd (garbage collected) later, and that takes time.See here for more info about subset assignment: https://cran.r-project.org/doc/manuals/r-release/R-lang.html#Subset-assignment
(Note that in some cases, R knows that there is only a single reference to the object, and when that happens, it will modify the object in place. That is what happens with
yin your example above, but not withx.)One way to avoid this extra copying is to use the
<<-operator instead. First, here's a demonstration of using it without R6 to show the speed difference:For the
self$x <- x+1case, notice the number of GC events, 4. And for they <<- y+1case, there are 0 GC events, and each iteration is about 9x faster on average.The reason
<<-is so much faster here is because, when you doy <<- y+1, it replacesydirectly in place, without making a copy.In order to use
<<-with an R6 object, you need to setportable=FALSE. Here's a modified version:When I run it, the
x$assign(1)does not cause a tracemem message to be printed, which is the same asy$assign(1).This will improve performance, but note that
portable=FALSEwill cause problems if you try to use inheritance with this class across packages. (https://r6.r-lib.org/articles/Portable.html)See my comments on this GitHub issue for a little more detail: https://github.com/r-lib/R6/issues/201#issuecomment-583486168