The problem with the D GC

Kyle Furlong kylefurlong at gmail.com
Tue Jan 9 14:16:09 PST 2007


Oskar Linde wrote:
> Oskar Linde wrote:
>> After having fought a while with D programs with runaway memory leaks, 
>> I've unfortunately had to come to the conclusion that the D GC is not 
>> ready for production use. The problem is what I'd call "spurious 
>> pointers". That is random data (strings, numbers, image data, audio or 
>> whatever) appearing to the GC to be full of pointers to all over the 
>> memory space.
> 
> I feel kind of bad for making it sound like this is a problem related 
> specifically to the D garbage collector. It is rather a general and 
> apparently well known problem of all conservative garbage collectors. 
> The D garbage collector is still excellent for a large domain of problems.
> 
> Lots of people seem to be having similar problems though, so a better 
> understanding of under what conditions a conservative garbage collector 
> will and will not work seems to be called for.
> 
> I'd guess that a lot of problems we have with the conservative GC is 
> because we are at the end of the life time of the 32-bit address space. 
> As time has passed, our programs have used a larger and larger portion 
> of the available memory space. As one anonymous %u said, one solution 
> would be "increasing the amount of memory needed for pointers by the 
> factor two", which I interpret as moving to a 64-bit memory space.
> 
> I did some calculations to better understand what is going on. I've 
> probably done several errors though.
> 
> <theoretical rambling>
> The probability of one single uniformly random spurious pointer 
> preventing the collection of an object of size s is
> 
> p = s/B,
> 
> with B being the size of the address space (2^b for b = 32 or 64 bits). 
> On a 64 bit architecture, the probability is obviously drastically reduced.
> 
> if g is the amount (bytes) of independent random data an application 
> needs, the risk of an object of size s not being collected is
> 
> P(g) = 1 - (1 - s/B) ^ (g/(b/8))
> 
> As one can see, the risk increases considerably as the objects gets 
> bigger. Increasing the amount of random data the application contains, 
> reduces the size of objects the GC will be able to handle satisfactory.
> 
> My example was kind of nasty in that it not only accumulated additional 
> random garbage in each iteration, but also caused heap fragmentation. A 
> kinder example would always create objects of the same size. The simpler 
> example would be:
> 
> * start with a static set of g bytes of random data
> * for each iteration create n objects of size s (for simplicity, let 
> each object contain s bytes of random data)
> * after each iteration, none of the n objects are used anymore. This is 
> a good time to call gc.fullCollect();
> 
> After each such iteration, i, P_i*n_i objects will remain uncollected, 
> and will be added to the pool of random spurious pointers. I disregard 
> the small portion of objects that are uncollected because of spurious 
> pointers appearing in other objects left uncollected in the same 
> iteration. If this portion would be significant, you'd really have a 
> problem anyway and this will show up in later iterations.
> 
> g_{i+1} ~ g_i + P_i*n_i*s.
> 
> I generously assume that the safe memory area of the collected objects 
> are reused by the allocator in the next iteration. A few of those will 
> become unsafe by the recently added spurious pointers from uncollected 
> objects, but for the most part, those memory areas are now safe, and the 
> objects allocated/collected from them can be disregarded. The number of 
> objects that need to be allocated in unsafe memory areas for the next 
> iteration becomes:
> 
> n_{i+1} = P_i*n_i + P(P_i*n_i*s)*(n_0 - n_i)
> 
> This will of course not perfectly predict the real behavior, as it 
> relaxes the problem away from discrete units, but should still 
> adequately model the sample application. Using this, I tried to find the 
> breaking point, i.e. at what size objects need to be explicitly freed 
> instead of left to the GC.
> 
> "Verification":
> Rewriting my original example to to a std.gc.fullCollect() every 10th 
> iteration, giving the following parameters:
> g = 20 MB
> n = 10
> 
> the model suggests allocating objects of size:
> s = 2000 would result in almost no space overhead, stable at 20 mb
> s = 3000 would result in a slight but stable space overhead,
> s = 4000 would cause a run-away memory usage
> 
> running the program results in:
> s = 2000 memory usage is stable at 20 mb
> s = 3000 results in a small apparently unbounded memory leak
> s = 4000 results in a unbounded memory leak
> 
> The model appears to be at least in the correct ballpark for this sample.
> 
> Theoretical results:
> Those tables show the maximum object size the GC will be able to handle 
> with different static amounts of "random" data. By "being able to 
> handle", I mean that the application doesn't use more than 2x the 
> required amount of memory. The exact breaking point is very unstable and 
> going above it rapidly results in uncontrolled memory consumption.
> 
> 32 bit arch, 100 objects
> 
> 1 MB data    8000 bytes
> 5 MB data    4500 bytes
> 10 MB data    3000 bytes
> 20 MB data    2000 bytes
> 100 MB data     700 bytes
> 500 MB data     200 bytes
> 1000 MB data     100 bytes
> 
> 32 bit arch, 1000 objects
> 
> 1 MB data    3400 bytes
> 5 MB data    2200 bytes
> 10 MB data    1700 bytes
> 20 MB data    1200 bytes
> 100 MB data     500 bytes
> 500 MB data     150 bytes
> 1000 MB data     100 bytes
> 
> 32 bit arch, 10000 objects
> 1 MB data    1300 bytes
> 5 MB data    1000 bytes
> 10 MB data     800 bytes
> 20 MB data     600 bytes
> 100 MB data     300 bytes
> 500 MB data     100 bytes
> 1000 MB data      75 bytes
> 
> Those figures need to be taken with a couple of grains of salt, but 
> should give a indication of at what object size one needs to manually 
> handle object lifetimes.
> 
> As a comparison -- the 64 bit haven:
> 
> 64 bit arch, 100 objects
> 2 GB data    1.5 GB
> 100 GB data    600 MB
> 1 TB data    200 MB
> 
> 64 bit arch, 1000 objects
> 2 GB data    350 MB
> 100 GB data    250 MB
> 1 TB data    150 MB
> 
> 64 bit arch, 10000 objects
> 2 GB data    100 MB
> 100 GB data     75 MB
> 1 TB data     50 MB
> </theoretical rambling>
> 
> Summary:
> 
> As far as I can see, what I have to do to avoid memory leaks with a 
> conservative GC, is one of the following:
> 
> 1. move to a 64 bit architecture
> 2. manually handle all objects larger than a few hundred bytes (see above)
> 3. hide all non pointer data from the GC
> 
> It is a shame #2 is such a pain and that D doesn't offer any help such 
> as automatic ref-counting. Not having automatic ref-counting also 
> prevents neat solutions such as transparent CoW, and automatic handling 
> of scarce resources.
> 
> If I wouldn't have a strong belief that automatic ref-counting would be 
> addressed soon, I'd definitely consider going back to C++. Luckily, 
> before I'd give up waiting, 64 bit architectures will probably be in 
> great majority. ;)
> 
> /Oskar

Dont hold your breath for a reference counting implementation from DMD. 
Walter doesnt want to add any overhead to assignments.



More information about the Digitalmars-d mailing list