The key to making D's GC acceptable lies in two factors I believe.

1. Improve the implementation enough so that you will only be impacted by GC in extermely low memory or real time environments. 2. Defer allocation more and more by using ranges and algorithms more, and trust that compiler optimisations will make these fast.

The big, big offender I believe for extra allocations is functions which return objects, rather than functions which write to output ranges. The single most common occurence of this is something like this is toString. Instead of writing this...

string toString() {
    // Allocations the user of the library has no control over.
    return foo.toString() ~ bar.toString() ~ " something else";
}

I believe you should always, always instead write this.

// I left out the part with different character types.
void writeString(OutputRange)(OutputRange outputRange)
if (isOutputRange!(OutputRange, char)) {
    // Allocations controlle by the user of the library,
    // this template could appear in a @nogc function.
    foo.writeString(outputRange);
    bar.writeString(outputRange);

    "something else".copy(outputRange);
}

It's perhaps strange at first because you're pre-programmed from other languages, except maybe C++ which uses output streams, to always be allocating temporary objects everywhere, even if all you are doing is writing them to an object.

For improving the GC to an acceptable level, I believe collection only needs to execute fast enough such that it will fit within a frame comfortably. So for something rendering at 60FPS you have 1 second / 60 frames ~= 16.6 milliseconds of computation you can do without resulting in a single dropped frame. That means you need to get collection down to something in the 1ms to 2ms region. At which point collection time will only impact something which is really pushing the hardware, which would exclude most mobile video games, which are about the complexity of Angry Birds.

I firmly believe there's no silver bullet for automatic memory management. Reference counting solutions, including automatic reference counting, will consume less memory than a garbage collector and offer more predictable collection times, but do so at the expense of memory safety and simplicity. You need fatter pointers to manage the reference counts, and you need to carefully deal with reference cycles.

In addition, you cannot easily share slices of memory with reference counting, which is an advantage of garbage collection. With GC, you can allocate a string, slice a part of it, hand over the slice to some other object, and you know that the slice will stay around for as long as it's needed. With reference counting, you have to either retain the slice and the whole segment in the same way and allow for the possibility of hidden cycles, or disallow slicing and create copies instead. Slicing in GC is important, because you can create much more efficient programs which take slices based on regex, which we do right now.

For the environments which cannot tolerate collection whatsoever, like Sociomantic's real time bidding operations, then control of allocation will have to be left to the user. This is where the zero allocation idea behind ranges and algorithms comes into play, because then the code which doesn't allocate, which could potentially be all of std.algorithm, can still be used in those environments, rather than being rendered unusable.

There's my thoughts on it anyway. I probably rambled on too long.

Reply via email to