www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - How does a free list work?

reply Pavel Shkadzko <p.shkadzko gmail.com> writes:
I have been reading about memory management in D on 
https://wiki.dlang.org/Memory_Management and found an example of 
a free list (pattern?): "Free lists are a great way to accelerate 
access to a frequently allocated and discarded type.".

Here is the example of free list:
-----------------------------------------------
class Foo
{
     static Foo freelist; // start of free list
     Foo next; // for use by FooFreeList

     static Foo allocate()
     {
         Foo f;

	if (freelist)
         {
             f = freelist;
	    freelist = f.next;
	}
         else
	    f = new Foo();
	    return f;
     }

     static void deallocate(Foo f)
     {
	f.next = freelist;
	freelist = f;
     }
}
-----------------------------------------------

Do I understand correctly that by switching between static and 
non-static Foo we keep the object from being garbage collected by 
the GC? So in a situation when I need to create an object and 
then discard it, I can implement this pattern to use memory more 
efficiently.

Also, it's a little strange that since both f and freelist are 
null we are basically doing null = null in first if condition.
May 09 2020
parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Saturday, 9 May 2020 at 19:54:44 UTC, Pavel Shkadzko wrote:
 I have been reading about memory management in D on 
 https://wiki.dlang.org/Memory_Management and found an example 
 of a free list (pattern?): "Free lists are a great way to 
 accelerate access to a frequently allocated and discarded 
 type.".

 Here is the example of free list:
 -----------------------------------------------
 class Foo
 {
     static Foo freelist; // start of free list
     Foo next; // for use by FooFreeList

     static Foo allocate()
     {
         Foo f;

 	if (freelist)
         {
             f = freelist;
 	    freelist = f.next;
 	}
         else
 	    f = new Foo();
 	    return f;
     }

     static void deallocate(Foo f)
     {
 	f.next = freelist;
 	freelist = f;
     }
 }
 -----------------------------------------------

 Do I understand correctly that by switching between static and 
 non-static Foo we keep the object from being garbage collected 
 by the GC? So in a situation when I need to create an object 
 and then discard it, I can implement this pattern to use memory 
 more efficiently.

 Also, it's a little strange that since both f and freelist are 
 null we are basically doing null = null in first if condition.
The GC keeps a list of roots - that is, objects that are known to be active and should not be collected. The static freelist is one of those - since it's static it should of course never be collected. From these roots, the GC scans all referenced objects recursively, and finally releases all memory that has not been scanned (and that thus have no path of pointers leading from a root to that memory). Since any call to new will check if memory is available, and potentially trigger a GC collection, it can be expensive, so if you can avoid allocating and deallocating objects a lot, it's an easy optimization. To more clearly show this, here's some code that prints what it does: import std.stdio : writeln; class Foo { static Foo freelist; Foo next; string name; this(string name) { this.name = name; } ~this() { writeln("GC collecting ", name); } static Foo allocate(string name) { if (freelist) { auto f = freelist; freelist = f.next; writeln("Fetching ", f.name, " from freelist, changing name to ", name); f.name = name; return f; } writeln("Nothing in freelist, allocating new ", name); return new Foo(name); } static void deallocate(Foo f) { writeln("Adding ", f.name, " to freelist, freelist.next points to ", freelist ? freelist.name : "(null)"); f.next = freelist; freelist = f; } } unittest { Foo a = Foo.allocate("A"); Foo b = Foo.allocate("B"); Foo c = Foo.allocate("C"); Foo.deallocate(a); Foo.deallocate(b); a = null; b = null; c = null; import core.memory; GC.collect(); // For some reason I need to call this twice for C to be collected? GC.collect(); Foo d = Foo.allocate("D"); Foo e = Foo.allocate("E"); Foo f = Foo.allocate("F"); } The above code creates this output: Nothing in freelist, allocating new A Nothing in freelist, allocating new B Nothing in freelist, allocating new C Adding A to freelist, freelist.next points to (null) Adding B to freelist, freelist.next points to A GC collecting C Fetching B from freelist, changing name to D Fetching A from freelist, changing name to E Nothing in freelist, allocating new F 1 unittests passed GC collecting E GC collecting D GC collecting F Here's what it does in more words: For the first call to allocate(), the freelist is null, and a new Foo is created in the 'else' path, before being returned. Nothing is assigned to freelist or next. The second and third call does the exact same thing, since nothing has been assigned to the freelist. We then deallocate a, which assigns it to the freelist. Next we deallocate b, which sets b's 'next' field to point at a, and sets freelist to point at b. We then set a, b, and c to null, so those references will no longer keep the Foos alive. Then we call GC.collect, which finds that the Foo previously stored in b is now in freelist, and thus will be kept. The Foo that was in a is referenced by freelist.next, and will also live. The foo in c, however, is no longer referenced anywhere, and will be collected. This shows the main point of the freelist - to ensure the objects aren't collected by the GC - but what happens afterwards? When we allocate d, freelist points at the Foo that used to be stored in b, so it is returned from allocate(), and the freelist is changed to point to the Foo that used to be in a. Allocating e there's still something in freelist, so it is returned. At this point the freelist is empty, and allocating f creates a new Foo, just like when we allocated a, b, and c. -- Simen
May 09 2020
parent Pavel Shkadzko <p.shkadzko gmail.com> writes:
On Saturday, 9 May 2020 at 22:48:46 UTC, Simen Kjærås wrote:
 On Saturday, 9 May 2020 at 19:54:44 UTC, Pavel Shkadzko wrote:
 [...]
The GC keeps a list of roots - that is, objects that are known to be active and should not be collected. The static freelist is one of those - since it's static it should of course never be collected. [...]
Thank you for such a detailed answer!
May 12 2020