Is `undefined` garabage collectable?

# /#!/JoePea (8 years ago)

For example, I have some code that uses a Map just to keep a collection of things (the keys) but values are not important, so they are undefined, like this:

let something = {}
let otherThing = {}
let m = new Map

m.set(something)
m.set(
​otherThing​
)

where the values for those object keys are undefined since there's not second arg to m.set. If I add the line

m.clear()

and retain references to something, otherThing, and m, I know that those objects won't be GCed. What about the undefined values? Are those undefined values something that the GC has to collect? Or do undefined values literally reference nothing, not needing to be collected?

Just wondering because I want to avoid GC while rendering animations at 60fps. I know I can prevent GC if I retain a value to some constant, as in

let something = {}
let otherThing = {}
const foo = true
let m = new Map

m.set(something, foo)
m.set(
​otherThing​, foo
)

m.clear()

so then if I retain the reference to foo then there's no GC; I'm just sticking things in and out of the Map, but I'm curious to know how undefined is treated, because if that prevents GC, then the code can be cleaner.

# Steve Fink (8 years ago)

On 05/04/2016 01:43 PM, /#!/JoePea wrote:

For example, I have some code that uses a Map just to keep a collection of things (the keys) but values are not important, so they are undefined, like this:

let something = {}
let otherThing = {}
let m = new Map

m.set(something)
m.set(
​ otherThing​
)

where the values for those object keys are undefined since there's not second arg to m.set. If I add the line

m.clear()

and retain references to something, otherThing, and m, I know that those objects won't be GCed. What about the undefined values? Are those undefined values something that the GC has to collect? Or do undefined values literally reference nothing, not needing to be collected?

First, use Set instead of Map if a set is what you want.

Second, this is an implementation question, since current GC is unobservable except for performance and even with WeakRef or something making it observable, it wouldn't come into play for your example. So this isn't really the list for it.

But nobody's going to make undefined be a GC thing.

On the other hand, there are language-invisible entries in a hashtable hiding behind your Map or Set. And those might be GC-able. Heck, they probably will be, since you wouldn't really want to run out of memory if you repetitively threw stuff into a Set and clear()ed it out over and over again.

Just wondering because I want to avoid GC while rendering animations at 60fps. I know I can prevent GC if I retain a value to some constant, as in

let something = {}
let otherThing = {}
const foo = true
let m = new Map

m.set(something, foo)
m.set(
​otherThing​ , foo
)

m.clear()

so then if I retain the reference to foo then there's no GC; I'm just sticking things in and out of the Map, but I'm curious to know how undefined is treated, because if that prevents GC, then the code can be cleaner.

In practical terms, the behavior with undefined is not going to have any more GCs. But are you sure the above is completely GC-safe?

For example, in SpiderMonkey, the below GCs 30 times when run from the shell:

var o1 = {}; var o2 = {}; var s = new Set();

for (i = 0; i < 10000000; i++) { s.add(o1); s.add(o2); s.clear(); }

If I remove the s.add lines, it will GC 2 times (there are 2 forced shutdown GCs).

The only v8 shell I have lying around is too old (3.14.5.10) to have Set, so I can't tell you what it would do.

# Boris Zbarsky (8 years ago)

On 5/4/16 5:03 PM, Steve Fink wrote:

The only v8 shell I have lying around is too old (3.14.5.10) to have Set, so I can't tell you what it would do.

I have v8 "4.8.0 (candidate)" (meaning whatever rev I checked out), and it does 1163 minor ("Scavenge") GCs on your testcase. It also does 1163 minor GCs if I take out the add() calls. It does none if I remove the clear() calls, no matter whether the add() calls are there or not.

# /#!/JoePea (8 years ago)

The only v8 shell I have lying around is too old (3.14.5.10) to have Set,

so I can't tell you what it would do.

On my first attempt, I noticed 8 Major GCs: cloud.githubusercontent.com/assets/297678/15036715/f41ea4d4-1247-11e6-8823-f153c3c1b7bb.png

On second attempt, no Major GCs: cloud.githubusercontent.com/assets/297678/15036788/c066483a-1248-11e6-970b-3f9d20710bbc.png

I wonder why. I'm in Chrome 50.

So, the second attempt looks good. I'm not sure why the first is so different. I tried it a few times, but I only got that Major GC zig-zag the first time.

Thanks for pointing out Set!

# Andreas Rossberg (8 years ago)

The undefined value is represented in exactly the same way as true, false, and null in V8. They're so called "oddballs" internally, global values that are neither allocated nor freed.

Either way, the key/values of a (regular) map do not keep anything alive about the map. Adding or removing entries from a set or map can of course cause allocation/deallocation, regardless of the keys/values themselves. (Similarly if you are using regular objects as maps, btw.)

There are also lots of other things that cause allocation/deallocation under the hood of a JavaScript engine, e.g. compilation, optimisation, deoptimisation, etc. Some of that predominantly happens at start-up. If you want to reduce random noise from your experiments, try to warm up the code first. But even then, don't read too much into micro-benchmarks -- JS VMs are far too complicated, dynamic, and heuristics-based to draw useful conclusions from tiny tests (that's e.g. why JSPerf tests are often bollocks).

In general, there is very little you can do in JavaScript that does not potentially cause allocation and thus GC. Certainly nothing involving dynamic data structures.

# /#!/JoePea (8 years ago)

Thanks Andreas, that's helpful to know. In general, is there some way to keep a list (adding removing things over time) while avoiding GC?

For example, I thought I could place and remove items into a list (f.e. Set or Map, while having my own references to all the items and the list and outside of the list as well as to the list itself), but as you pointed out that still can have need for GC.

I know you said

In general, there is very little you can do in JavaScript that does not

potentially cause allocation and thus GC

But, I would at least want to know that I minimized GC as much as possible. My first hunch (for my case) would be to make a linked list out of the items I need to have a "list" of, where each item's link (reference) to the next item is also one of the references that I already hold outside of the list?

It seems that with Arrays (and Sets and Maps) that deleting an item from the list also deletes the placeholder that was used for that item (in the case of Array, the placeholders would be the numerical keys that are deleted when an item is popped for example), and I'm guessing (with my still expanding VM knowledge) that those placeholders are GCed separately from the thing that was referenced to by the placeholder (so even if the placeholder contained null there would still be GC). Is that a good guess?

I'll be experimenting...

# Isiah Meadows (8 years ago)

I'll just point this out: low level optimization like that is very unintuitive.

Game engines often use object pools to avoid allocation and GC, since even a single malloc is sometimes too expensive. There's other ways of reducing GC as well, such as persistence (like persistent data structures - object pooling is a similar concept), changing your algorithm to work in place, changing your algorithm to have fewer memory requirements, or even explicit marking of an object to be collected for GC while it's young (engines already often do some of their own pooling of frequently created objects).

As for language help, you're on your own, because there's little the language can help you with, regardless of what's added.

# Andreas Rossberg (8 years ago)

Arrays are probably your best bet (but don't ever delete elements, or change the size of your array!).

I don't understand what you mean by "placeholder". But FWIW, array keys (i.e., small integers) are usually unboxed, i.e., not heap-allocated.

A general advise, though: don't be over-concerned with GC. There are a lot of misconceptions about its performance. It is quite efficient in most practical cases -- much more so than most attempts to work around it and manage life-times manually.