GC memory fragmentation

tchaloupka chalucha at gmail.com
Wed Apr 14 11:00:15 UTC 2021


On Tuesday, 13 April 2021 at 12:30:13 UTC, tchaloupka wrote:
> I'm not so sure if pages of small objects (or large) that are 
> not completely empty can be reused as a new bucket or only free 
> pages can be reused.
>
> Does anyone has some insight of this?
>
> Some kind of GC memory dump and analyzer tool as mentioned 
> `Diamond` would be of tremendous help to diagnose this..


As now it's possible to inject own GC to runtime, I've tried to 
compile debug version of the GC implementation and use that to 
check some debugging printouts with regard to small allocations.

The code I've used to simulate the behavior (with 
`--DRT-gcopt=incPoolSize:0`):

```D
// fill 1st pool (minsize is 1MB so it's 256 pages, two small 
objects in each
ubyte[][512] pool1;
foreach (i; 0..pool1.length) pool1[i] = new ubyte[2042];

// no more space in pool1 so new one is allocated and also filled
ubyte[][512] pool2;
foreach (i; 0..pool2.length) pool2[i] = new ubyte[2042];

// free up first small object from first pool first page
pool1[0] = null;
GC.collect();

// allocate another small object
pool1[0] = new ubyte[2042];

```

And shortened result is:

```
// first pool allocations
GC::malloc(gcx = 0x83f6a0, size = 2044 bits = a, ti = TypeInfo_h)
Gcx::allocPage(bin = 13)
Gcx::allocPage(bin = 13)
Gcx::newPool(npages = 1, minPages = 256)
Gcx::allocPage(bin = 13)
   => p = 0x7f709e75c000

// second pool allocations
GC::malloc(gcx = 0x83f6a0, size = 2044 bits = a, ti = TypeInfo_h)
Gcx::allocPage(bin = 13)
Gcx.fullcollect()
preparing mark.
         scan stacks.
         scan roots[]
         scan ranges[]
                 0x51e4e0 .. 0x53d580
         free'ing
         free'd 0 bytes, 0 pages from 1 pools
         recovered small pages = 0
Gcx::allocPage(bin = 13)
Gcx::newPool(npages = 1, minPages = 256)
Gcx::allocPage(bin = 13)
   => p = 0x7f709e65c000

// collection of first item of first pool first bin
GC.fullCollect()
Gcx.fullcollect()
preparing mark.
         scan stacks.
         scan roots[]
         scan ranges[]
                 0x51e4e0 .. 0x53d580
         free'ing
         collecting 0x7f709e75c000
         free'd 0 bytes, 0 pages from 2 pools
         recovered small pages = 0

// and allocation to test where it'll allocate
GC::malloc(gcx = 0x83f6a0, size = 2044 bits = a, ti = TypeInfo_h)
   recover pool => 0x83d1a0
   => p = 0x7f709e75c000
```

So if I'm not mistaken, bins in pools are reused when there is a 
free space (ast last allocation reuses address from first item in 
first bin in first pool).

I've also tested big allocations with same result.

But if this is so, I don't understand why in our case there is 
95% free space in the GC and it still grows from time to time.

At least rate of grow is minimized with 
`--DRT-gcopt=incPoolSize:0`

Would have to log and analyze the behavior more with a custom GC 
build..k


More information about the Digitalmars-d-learn mailing list