std.allocator ready for some abuse

Andrei Alexandrescu SeeWebsiteForEmail at erdani.org
Thu Oct 24 14:18:00 PDT 2013


On 10/24/13 1:46 PM, bearophile wrote:
> Andrei Alexandrescu:
>
>> and morph and combine in infinite ways.
>
> Yes, those combination capabilities look very nice :-)
>
> I presume this module is not in "core" because it's meant to be used
> from normal D code.

Interesting - moving to core is a possibility if we find it necessary.

> - Could some new/extra D language/compiler support/features help this
> module usage safety, efficiency, expressibility or usage syntax (beside
> Issue 11331)?

I thought about that a lot, too, while I was working on the design. 
Kinda analyzing my own going. It's generally been a pleasant experience 
- i.e. whenever I'd find myself in a bind there was some nice way out of 
it. One thing that I found myself wishing is uniformly aliasing 
something to something else. There's plenty of code like:

static if (staticallyKnownAlignment!Primary
         && staticallyKnownAlignment!Fallback)
     enum uint alignment = min(Primary.alignment, Fallback.alignment);
else
     uint alignment()
     {
         return min(primary.alignment, fallback.alignment);
     }

This is aimed at making the alignment either a statically-known constant 
or a dynamic property, depending on two type parameters. There's not a 
lot of duplication but it's quite annoying to have to write this. Would 
be great if I could just write e.g.

alias alignment = min(primary.alignment, fallback.alignment);

and have the thing just automagically do the right thing.

There have been some good idioms I used gainfully, i.e. I think the use 
of unbounded and chooseAtRuntime could become quite common.

"Turtles all the way" are awesome because they allow me to get work done 
without having to pop up and down. Consider for example:

     bool reallocate(ref void[] b, size_t newSize)
     {
         bool crossAllocatorMove(From, To)(ref From from, ref To to)
         {
             auto b1 = to.allocate(newSize);
             if (!b1) return false;
             if (b.length < newSize) b1[0 .. b.length] = b[];
             else b1[] = b[0 .. newSize];
             static if (hasMember!(From, "deallocate"))
                 from.deallocate(b);
             b = b1;
             return true;
         }

         if (primary.owns(b))
         {
             if (primary.reallocate(b, newSize)) return true;
             // Move from primary to fallback
             return crossAllocatorMove(primary, fallback);
         }
         if (fallback.reallocate(b, newSize)) return true;
         // Interesting. Move from fallback to primary.
         return crossAllocatorMove(fallback, primary);
     }

This is code that moves memory across two allocators, in either 
directions. The original version duplicated the actual work. Then I 
considered a version that would define crossAllocatorMove as a private 
method. Then I tried the above, which just worked awesomely nice.

But above all there's the property of Things Just Working (tm). Compared 
to e.g. the time when I designed ranges, there's a lot less toil for a 
lot more effect. Consider e.g. the Segregator with multiple arguments:

template Segregator(Args...) if (Args.length > 3)
{
     // Binary search
     private enum cutPoint = ((Args.length - 2) / 4) * 2;
     static if (cutPoint >= 2)
     {
         alias Segregator = .Segregator!(
             Args[cutPoint],
             .Segregator!(Args[0 .. cutPoint], Args[cutPoint + 1]),
             .Segregator!(Args[cutPoint + 2 .. $])
         );
     }
     else
     {
         // Favor small sizes
         alias Segregator = .Segregator!(
             Args[0],
             Args[1],
             .Segregator!(Args[2 .. $])
         );
     }

     // Linear search
     //alias Segregator = .Segregator!(
     //    Args[0], Args[1],
     //    .Segregator!(Args[2 .. $])
     //);
}

Granted, it's not easy to get into, but can't be made much easier 
because it does something highly non-trivial: a left-leaning binary 
search through a subset of the template arguments. That's in order to 
find the best size class to allocate. (Linear version shown in comment, 
probably we should enable that with a policy.) Such code would have been 
much harder to get going in the past because of random bugs and 
limitations in the compiler and inscrutable error messages.

The entire code base stands at 4000 lines, documentation and all, yet 
allows for a lot of allocator designs.

> - Can you create an hierarchical allocator with those?

Not sure what that means (I recall the phrase has been discussed but 
forgot what it means).

> - Are some operating system memory functions (below C malloc)
> used/supported/addable/useful for those allocators? (Including awareness
> of virtual memory management from the OS).

Definitely!

* http://stackoverflow.com/questions/3839922/aligned-malloc-in-gcc comes 
to mind.

* http://en.wikipedia.org/wiki/Sbrk

* http://en.wikipedia.org/wiki/Mmap

These would be implemented as top allocators (a la Mallocator). On top 
thereof, nice block allocators etc. can be easily defined.

Also the final version should define a few convenience types that are 
pre-assembled allocators that are known to be useful: heaps, reaps, 
freelist batteries come to mind.


Andrei



More information about the Digitalmars-d mailing list