Garbage collection question

Hi, I’m realizing I don’t have a good grasp on what the conditions are for garbage collection – is it simply that anything not referenced by a variable gets garbage collected, or are there other conditions as well? (e.g. a TempoClock set to permanent? an object that still has registered dependants? child Views that haven’t been properly removed? …) are there common memory leak gotchas?

Broadly speaking, something will at some point in the future happen to things which aren’t reachable, except from a few permanent objects, like the classes. Additionally, the interpreter stores a number of variables which it keeps alive, curentEnvironement is one, TempoClocks (or was it Schedulers?) I believe are another of them (might be wrong here as I can’t remember).

When enough objects need to be deleted, supercollider ‘sweeps’ these up. This involves marking all the unreachable object as reusable (it actually cheats a little and does this all in one step), so that new objects in the future can make use of the memory. Also, objects are grouped into size bands to make sweeping faster, meaning you if only need x bytes you might get x+n bytes.

There are two exceptions to this.

Finalizers, currently there are bugs in this, which behave as normal, but are supposed to call a function pointer before they are recycled, otherwise they behave as normal. The user does not need to concern themselves with these unless doing a lot of GUI coding

Big object, when the memory footprint of the object is really big, which aren’t recycled, but released back to the operating system.

It is important to understand (if you are planning on digging into the code) that when memory is recycled is hard to predict and supercollider has its own heuristics about when this should happen. This is also the case for big objects!

There is currently a bug when you create functions as this will avoid triggering the GC due to the ‘jiggery–pokery’ require to bootstrap the interpreter. This only occurs if you make many functions really fast. I have a fix for this Sclang: PyrGC: Fix GC segfault caused by uncollected temporary function objects by placing an upper limit on un-scanned objects. by JordanHendersonMusic · Pull Request #6261 · supercollider/supercollider · GitHub but seems to have fallen silent.

Additionally, this only applied to things represented by PyrObject in the c++ code. Numbers and a few other small sized types are not garbage collected.

1 Like

Thanks for the thorough answer! I learned a lot, this was not at all how I imagined garbage collection to work, and it’s really impressive.

I don’t even know what finalizers are… I guess some qt thing?

I wasn’t just now planning on digging into the code base, more asking in terms of best practices for coding in sclang.

For example, I just saw in the Object helpfile:

.release

Remove all dependants of the receiver. Any object that has had dependants added must be released in order for it or its dependants to get garbage collected.

So this means, I think, that if you do

(
var a = ();
var b = { |...args| args.postln };
a.addDependant(b);
a = nil;
b = nil;
)

these two objects will never be garbage collected? Is this even something to worry about on the scale of a few thousand objects? and are there other common cases like this, where unreachable user-created objects are ungarbagecollectible?

Correct – the objects are then reachable through Object’s dependantsDictionary.

If you’re coding for an hour and then quitting SC, it’s not going to hurt you that badly. If you’re running an installation art piece for 8-10 hours a day, uncollected garbage could be an issue. It’s a better habit to remove dependencies of objects that aren’t needed anymore.

hjh

Whoa! Never knew that existed. Cool.

BTW one strategy for dependency removal is to have the model broadcast a message like didFree, to which dependants respond by removing themselves.

(
var sl = Slider(nil, Rect(800, 200, 150, 30)).front;

x = { |freq = 400| (SinOsc.ar(freq) * 0.1).dup }.play;

~model = (
	v: 400,
	v_: { |self, value|
		self[\v] = value;
		self.changed(\value, value);
	},
	dispose: { |self|
		self.changed(\didFree);
	}
);

~controller = SimpleController(~model)
.put(\value, { |obj, what, value|
	defer {
		sl.value = Spec.specs[\freq].unmap(value);
		x.set(\freq, value);
	}
})
.put(\didFree, {
	~controller.remove;
	defer { sl.remove };
});

sl.action = { |view| ~model.v = Spec.specs[\freq].map(view.value) };
sl.value = Spec.specs[\freq].unmap(~model.v);
)

Object.dependantsDictionary.keysValuesDo { |k, v|
	if(k.isKindOf(Event)) { [k, v].postln };
}; ""
->
[('dispose': a Function, 'v': 400.0, 'v_': a Function), IdentitySet[a SimpleController]]

~model.dispose;
x.free;

Object.dependantsDictionary.keysValuesDo { |k, v|
	if(k.isKindOf(Event)) { [k, v].postln };
}; ""
->
(nothing)

hjh

1 Like