Download Reference Manual
The Developer's Library for D
About Wiki Forums Source Search Contact

Ticket #878 (closed defect: fixed)

Opened 16 years ago

Last modified 16 years ago

Severe GC leaks with repetitive array allocations

Reported by: sean Assigned to: sean
Priority: major Milestone: 0.99.5
Component: Core Functionality Version: 0.99.4 Frank
Keywords: Cc:

Description

The following is copied from the DMD ticket:

Linux (Fedora 6), 64 bit

The following program will run out of memory when the allocations reach about 8
MB.  The problem happens on *both* 1.026 and 2.010

FYI: I tried running fullCollect() periodically, just in case...no luck.

  import std.stdio;
  void main()
  {
    for(int i=1; ; i++)
    {
      auto tmp = new char[i*1024];
      if(i % 1_000 == 0)
        writefln("%d iterations, roughly %.1f MB", i,
tmp.length/(1024*1024.0));
    }
  }

Change History

02/08/08 02:10:40 changed by sean

  • status changed from new to assigned.

Okay, I think I know what's going on. For big allocations, the GC will look for a pre-existing set of contiguous pages within a single pool to hold the block. If one cannot be found, then it creates a new pool of the requested size to hold the block. However, in this app the allocation size just continuously increases, so as time goes on the GC is unable to re-use existing pools and so allocates successively larger pools to hold the requested block. The problem is that all these old pools which were only used once are held by the GC for future use, so the total memory used by the application steadily increases, with most of this memory going completely unused.

The simplest fix in this case would be for the GC to always release pools obtained for big allocations back to the OS when they are no longer needed. This should address the pathological case demonstrated in this test app. I'm going to make this change in Tango and see if it helps.

02/08/08 03:21:46 changed by sean

  • status changed from assigned to closed.
  • resolution set to fixed.

(In [3155]) Added GC.minimize(), which returns the memory used by empty pools back to the OS. This routine is also called within Gcx.bigAlloc() after a collection has failed to produce a sufficient block of memory for the allocation, and before a new pool is created. This closes #878

02/08/08 06:00:03 changed by kris

  • status changed from closed to reopened.
  • resolution deleted.

I see a potential problem here:

I try to allocate a chunk of memory, and traverse each of the current pools looking for a suitably sized free space. None are found, so I now move onto creating a new pool for this allocation. At this time, all of the pools are now traversed once again, looking to see if they are empty or not (and can thus be released).

Is this an accurate portrait? If so, it seems somewhat inefficient to traverse the pools again. Perhaps there's some cheap means of identifying a pool as being used or not?

02/26/08 05:02:56 changed by sean

  • status changed from reopened to closed.
  • resolution set to fixed.

It's a bit more complicated than that. The easiest way to see what's going on is to look at the definition for bigAlloc around line 1896 of gcx.d.

One bit of behavior I do think is a bit weird but I haven't changed from Phobos is that bigAlloc will check existing pools for an adequately sized free chunk, if none is found then a collection will be performed and then if the collection freed up enough space the pools will be scanned again. Problem being that fullcollect just returns the total memory recovered rather than the size of the largest contiguous block recovered, so the secondary scan could be completely useless.

In short, I do think that there are some further optimizations that could be performed, but things really aren't too bad right now. The case I mentioned above is probably the most significant issue, and since the average app won't have more than but a few pools it really isn't all that bad. If you notice anything else though, please add it to this ticket. I'll close this again as I think the current behavior is reasonable.