News: Stay up to date

The Étoilé community is an active group of developers, designers, testers and users. New work is being done every day. Visit often to find out what we've been up to.


Garbage Collection and Pragmatic Smalltalk

Posted on 27 May 2011 by David Chisnall

I got a bit side-tracked yesterday, talking about the Cocoa APIs for garbage collection, when I meant to talk a bit more about what it means for Étoilé, so here's a second attempt:

It's very easy in Objective-C code to forget to send some required retain or release messages, and either end up with a use-after-free bug or a memory leak. Experienced Objective-C programmers add these without thinking, but getting to that stage takes a long time.

With Pragmatic Smalltalk, the compiler automatically inserts retain and release calls for you. It doesn't do it in quite an optimal way, because this would require complex dataflow analysis, but it does generate working code.

People expect garbage collection from Smalltalk, and the automatic reference counting is 'good enough' in about 99% of cases. The remaining 1% involves object graphs with cycles. I wrote an automatic cycle detector for use with LanguageKit, but I've never got around to enabling it.

With the latest code, Smalltalk enjoys exactly the same garbage collection as everything else. This means that:

  • Globals are marked as roots as soon as they are assigned (not relevant to Smalltalk)
  • Instance variables are treated as object pointers
  • Memory allocated with NSAllocateCollectable() and NSScannedOption is conservatively scanned.
  • The stack is conservatively scanned

This isn't quite as good as the accurate collection that you'll typically find in a Smalltalk implementation, but it's not far off. In pure Pragmatic Smalltalk code, the only pointers are instance variables, which are accurately scanned, or on the stack. Most Smalltalk implementations do accurate scanning of the stack, but this doesn't add much benefit in terms of performance - the overhead of computing and parsing the stack maps is quite large.

The big advantage of accurate garbage collection, as found in most Smalltalk, Java, and so on implementations, is that it allows the collector to be completely aware of the locations of all pointers. This means that it can move objects, by just updating their pointers. The Boehm GC can't do this yet, although it's possible that it could flag that an object is not referenced by any possible-pointers in the conservatively scanned regions, and then mark it as eligible for moving.

Because Smalltalk and Objective-C code are using exactly the same barrier calls, we get exactly the same performance from Smalltalk and Objective-C. We may even get better garbage collection performance from Smalltalk, because every SmallInt object has its low bit set to 1, so will be ignored by the compiler. In contrast, C integer types are easily confused with pointers, if they happen to contain even numbers.

From the point of view of someone writing Smalltalk code, GC in Objective-C should not require any changes to your code, it just means that now you cna be sure that garbage cycles are freed. Oh, and you'll probably see memory usage go down, because LanguageKit is quite conservative about autoreleasing instead of retaining.

You can, of course, force the garbage collector to run, in exactly the same way that Objective-C programmers do. If you look in Langauges/Compiler/ in subversion, you'll see this line a few times:

NSGarbageCollector defaultCollector collectExhaustively.

If you're not in garbage collected mode, then GNUstep will return nil to the first message, so the second will be silently ignored. If you are using GC, then the second call will tell the collector to keep trying to collect until it can't find any more free object. This line is largely here for testing that the collector isn't freeing anything that it shouldn't be. In normal code, you're more likely to want to write this:

NSGarbageCollector defaultCollector collectIfNeeded.

This will poke the collector to say that now is a good time to run.  It's not required, but it is a good idea.  The collector will be triggered to run at the end of each run loop, if you're using `NSRunLoop`, but otherwise it will run occasionally as a result of allocations, which may be at an inconvenient time.  If you've got some code that can't be interrupted by the collector, you can do this:

gc := NSGarbageCollector defaultCollector.
gc disable.
self doRealtimeStuff.
gc enable.
gc collectIfNeeded.

This will ensure that the collector does not run during the -doRealtimeStuff method invocation. Note that this may not actually improve response times. If you're allocating a lot of objects, then the allocation may become slower as you start swapping or even as you start requesting more pages from the OS - turning on the collector may actually make performance worse. If you're just allocating a few objects, however, then this block lets you ensure that the collector will run after your block of code, not during.