Copying R Environments

I’ve been working on a codebase that relied on storing a lot of objects in R environments, mainly because of the potential speed improvements with large numbers of objects.

See this article for a pretty good explanation.

After a recent spec change, I needed to start looping around a block of code that was previously using a single environment to store objects. The easiest approach was to create an initial base environment to use at the start of each iteration of the loop, and then create a copy of that environment that would be specific to the loop iteration.

But I got a surprise!

All of the result environments looked like the last iteration.

First, let’s look at how this works when you start with a base list, make a copy,  and modify the copy:

> original <- list()
> copy <- original

Because copy hasn’t been modified, it shares the same memory
address as​ original, for efficiency:

tracemem(original)
[1] "<0x4c5d738>"
> tracemem(copy)
[1] "<0x4c5d738>"

So let’s modify copy, and notice that it’s now copied to its own memory address:

> copy$item <- 1L
tracemem[0x4c5d738 -> 0x5a4c1b8]:

And if we now compare original and copy, we get completely expected differences:

> original
list()
> copy
$item
[1] 1

> original$item
NULL
> identical(original, copy)
[1] FALSE

 

Now let’s repeat with environments:

> original <- new.env()
> copy <- original

And if we use tracemem as before, we get a hint that something is different:

> tracemem(original)
Error in tracemem(original) : 
  'tracemem' is not useful for promise and environment objects

So if we simply print the environments, we’ll see the memory addresses:

> original
<environment: 0x69d6cc8>
> copy
<environment: 0x69d6cc8>
> identical(original, copy)
[1] TRUE

Now let’s modify copy, and notice that the memory address doesn’t change:

> copy$item <- 1L
> original
<environment: 0x69d6cc8>
> copy
<environment: 0x69d6cc8>

Presumably that means that original also now has an element named item, right?

> copy$item
[1] 1
> original$item
[1] 1
> identical(original, copy)
[1] TRUE

Right!

So if we remove the element item from original, it should also disappear from copy:

> rm('item', pos = as.environment(original))
> original$item
NULL
> names(original)
character(0)
> copy$item
NULL
> identical(original, copy)
[1] TRUE

So, if you’ve ever copied an existing environment thinking you could modify it independently of the original, you should revisit that code to make sure it’s not causing problems!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: