A few thoughts on std.allocator

Andrei Alexandrescu via Digitalmars-d digitalmars-d at puremagic.com
Sun May 10 02:50:00 PDT 2015


In C, C++, and D people have allocated memory for a long time in the 
following manner:

1. Allocate as many bytes as needed (e.g. by using malloc);
2. Mess with the memory allocated.

(C++ took this one step further by defining class-specific allocators, 
feature that D copied. That turned out to be little else than a lateral 
step. C++ also offers distinct allocation for arrays vs. anything else, 
but because it lacks typed reallocation primitives, it effectively 
hamstrung that feature's capabilities. Also the STL introduced 
allocators, which turned out to not do much of anything useful except 
being an immense time sink for everyone involved with them in any 
capacity. I call these cumulatively The Big Distraction.)

I've just had a mini-epiphany that's been buzzing around my head for a 
while. Finally it got to a form expressible in words:

====
Type should inform allocation.
====

I.e. you shouldn't first allocate and then brand the memory with a type. 
Instead, you should make the type known _during_ the allocation process.

This is not new and on the face of it not even all that noteworthy: (a) 
some high-level languages did this for years, (b) it's unclear which 
exact properties of a type should be relevant to the allocation.

Within the context of D, there are some interesting aspects of a type 
that a complex modular allocator could use to great effect. Consider:

1. Individual objects (i.e. not arrays)

When allocating an individual object, there is virtually zero chance 
that object will ever need to be resized. (There are exceptions such as 
"the struct hack" but those would be handled together with arrays.) 
There are two important consequences of this:

(a) Individual object sizes are already quantized, i.e. there is a 
relatively small set of distinct sizes compared to the number of objects 
being allocated.

(b) Individual objects can be placed in an allocator that's arbitrarily 
adverse to reallocation (i.e. has no in-place expansion ability and no 
clever reallocation primitives). It seems like FreeTree 
(file:///Users/aalexandre/code/d/dlang.org/web/phobos-prerelease/std_experimental_allocator_free_tree.html) 
would be an excellent fit for allocating individual objects. HeapBlock 
also comes to mind.

2. Arrays

The range of sizes allocated for arrays is quite a bit larger and more 
arbitrary than for individual objects. Arrays still grow in quanta (e.g. 
T.sizeof hops) but the set of distinct sizes is much larger and much 
less predictable.

One other interesting thing is that arrays offer concatenation and 
resizing as primitive operations, so it follows that their underlying 
allocator should be friendly to such. This leads to an allocator design 
for arrays that's radically different from the allocator design for 
individual objects.

Long story short, arrays should sit on a different heap than objects.

3. Thread-local vs. shared objects

Currently in D it's legal to allocate memory in one thread and 
deallocate it in another. (One simple way to look at it is casting to 
shared.) This has a large performance cost that only benefits very few 
actual cases.

It follows that we need to change the notion that you first allocate 
memory and then brand it as shared. The "will be shared" knowledge must 
be present during allocation, and use different methods of allocation 
for the two cases.

4. "Has pointers" vs. "has no pointers"

When collecting a specific object, it's important to know whether its 
type embeds pointers. If it does, more scanning is necessary. Otherwise, 
the object is a "leaf" leading to no others.

It seems to me we stand to gain by physically and conceptually 
separating memory that stores leaf objects from that dedicated to 
non-leaf objects. Making the scan-heavy memory more compact leads to 
better cache friendliness.

Currently D's allocator allows choosing at runtime for each allocated 
block whether is should be scanned. The new design would require making 
the choice upfront. There would be, of course, the "anything goes" heap 
that still allows that level of dynamism.

5. "Plan to deallocate" vs. "plan to let the GC mind this"

Objects that will be deallocated manually deserve different placement 
compared to those that will only be deallocated during a GC cycle.

6. Immutable vs. mutable

I'm not sure how this can be practically exploited. There must be 
something in the notion that, save for a brief write period after 
allocation, the memory will stay unmodified. Still thinking.

7. Strings

On the face of it strings are immutable arrays, so they've been already 
discussed. However, they have some interesting characteristics:

(a) small alignment, e.g. 1 byte

(b) often extremely short, e.g. 1-8 bytes

(c) concatenation intensive (including append)

(d) perhaps other unique properties and patterns that elude me

So... types should inform allocation.


Please chime in!

Andrei


More information about the Digitalmars-d mailing list