phobos/std/experimental/allocator/segregator.d
2015-10-02 07:35:08 -04:00

386 lines
13 KiB
D

module std.experimental.allocator.segregator;
import std.experimental.allocator.common;
/**
Dispatches allocations (and deallocations) between two allocators ($(D
SmallAllocator) and $(D LargeAllocator)) depending on the size allocated, as
follows. All allocations smaller than or equal to $(D threshold) will be
dispatched to $(D SmallAllocator). The others will go to $(D LargeAllocator).
If both allocators are $(D shared), the $(D Segregator) will also offer $(D
shared) methods.
*/
struct Segregator(size_t threshold, SmallAllocator, LargeAllocator)
{
import std.algorithm : min;
import std.traits : hasMember;
static if (stateSize!SmallAllocator) SmallAllocator _small;
else static alias SmallAllocator.it _small;
static if (stateSize!LargeAllocator) LargeAllocator _large;
else alias LargeAllocator.it _large;
version (StdDdoc)
{
/**
The alignment offered is the minimum of the two allocators' alignment.
*/
enum uint alignment;
/**
This method is defined only if at least one of the allocators defines
it. The good allocation size is obtained from $(D SmallAllocator) if $(D
s <= threshold), or $(D LargeAllocator) otherwise. (If one of the
allocators does not define $(D goodAllocSize), the default
implementation in this module applies.)
*/
static size_t goodAllocSize(size_t s);
/**
The memory is obtained from $(D SmallAllocator) if $(D s <= threshold),
or $(D LargeAllocator) otherwise.
*/
void[] allocate(size_t);
/**
This method is defined if both allocators define it, and forwards to
$(D SmallAllocator) or $(D LargeAllocator) appropriately.
*/
void[] alignedAllocate(size_t, uint);
/**
This method is defined only if at least one of the allocators defines
it. If $(D SmallAllocator) defines $(D expand) and $(D b.length +
delta <= threshold), the call is forwarded to $(D SmallAllocator). If $(
LargeAllocator) defines $(D expand) and $(D b.length > threshold), the
call is forwarded to $(D LargeAllocator). Otherwise, the call returns
$(D false).
*/
bool expand(ref void[] b, size_t delta);
/**
This method is defined only if at least one of the allocators defines
it. If $(D SmallAllocator) defines $(D reallocate) and $(D b.length <=
threshold && s <= threshold), the call is forwarded to $(D
SmallAllocator). If $(D LargeAllocator) defines $(D expand) and $(D
b.length > threshold && s > threshold), the call is forwarded to $(D
LargeAllocator). Otherwise, the call returns $(D false).
*/
bool reallocate(ref void[] b, size_t s);
/**
This method is defined only if at least one of the allocators defines
it, and work similarly to $(D reallocate).
*/
bool alignedReallocate(ref void[] b, size_t s);
/**
This method is defined only if both allocators define it. The call is
forwarded to $(D SmallAllocator) if $(D b.length <= threshold), or $(D
LargeAllocator) otherwise.
*/
bool owns(void[] b);
/**
This function is defined only if both allocators define it, and forwards
appropriately depending on $(D b.length).
*/
void deallocate(void[] b);
/**
This function is defined only if both allocators define it, and calls
$(D deallocateAll) for them in turn.
*/
void deallocateAll();
/**
This function is defined only if both allocators define it, and returns
the conjunction of $(D empty) calls for the two.
*/
bool empty();
}
/**
Composite allocators involving nested instantiations of $(D Segregator) make
it difficult to access individual sub-allocators stored within. $(D
allocatorForSize) simplifies the task by supplying the allocator nested
inside a $(D Segregator) that is responsible for a specific size $(D s).
Example:
----
alias A = Segregator!(300,
Segregator!(200, A1, A2),
A3);
A a;
static assert(typeof(a.allocatorForSize!10) == A1);
static assert(typeof(a.allocatorForSize!250) == A2);
static assert(typeof(a.allocatorForSize!301) == A3);
----
*/
ref auto allocatorForSize(size_t s)()
{
static if (s <= threshold)
static if (is(SmallAllocator == Segregator!(Args), Args...))
return _small.allocatorForSize!s;
else return _small;
else
static if (is(LargeAllocator == Segregator!(Args), Args...))
return _large.allocatorForSize!s;
else return _large;
}
enum uint alignment = min(SmallAllocator.alignment,
LargeAllocator.alignment);
template Impl()
{
size_t goodAllocSize(size_t s)
{
return s <= threshold
? _small.goodAllocSize(s)
: _large.goodAllocSize(s);
}
void[] allocate(size_t s)
{
return s <= threshold ? _small.allocate(s) : _large.allocate(s);
}
static if (hasMember!(SmallAllocator, "alignedAllocate")
&& hasMember!(LargeAllocator, "alignedAllocate"))
void[] alignedAllocate(size_t s, uint a)
{
return s <= threshold
? _small.alignedAllocate(s, a)
: _large.alignedAllocate(s, a);
}
static if (hasMember!(SmallAllocator, "expand")
|| hasMember!(LargeAllocator, "expand"))
bool expand(ref void[] b, size_t delta)
{
if (b.length + delta <= threshold)
{
// Old and new allocations handled by _small
static if (hasMember!(SmallAllocator, "expand"))
return _small.expand(b, delta);
else
return false;
}
if (b.length > threshold)
{
// Old and new allocations handled by _large
static if (hasMember!(LargeAllocator, "expand"))
return _large.expand(b, delta);
else
return false;
}
// Oops, cross-allocator transgression
return false;
}
static if (hasMember!(SmallAllocator, "reallocate")
|| hasMember!(LargeAllocator, "reallocate"))
bool reallocate(ref void[] b, size_t s)
{
static if (hasMember!(SmallAllocator, "reallocate"))
if (b.length <= threshold && s <= threshold)
{
// Old and new allocations handled by _small
return _small.reallocate(b, s);
}
static if (hasMember!(LargeAllocator, "reallocate"))
if (b.length > threshold && s > threshold)
{
// Old and new allocations handled by _large
return _large.reallocate(b, s);
}
// Cross-allocator transgression
return .reallocate(this, b, s);
}
static if (hasMember!(SmallAllocator, "alignedReallocate")
|| hasMember!(LargeAllocator, "alignedReallocate"))
bool reallocate(ref void[] b, size_t s)
{
static if (hasMember!(SmallAllocator, "alignedReallocate"))
if (b.length <= threshold && s <= threshold)
{
// Old and new allocations handled by _small
return _small.alignedReallocate(b, s);
}
static if (hasMember!(LargeAllocator, "alignedReallocate"))
if (b.length > threshold && s > threshold)
{
// Old and new allocations handled by _large
return _large.alignedReallocate(b, s);
}
// Cross-allocator transgression
return .alignedReallocate(this, b, s);
}
static if (hasMember!(SmallAllocator, "owns")
&& hasMember!(LargeAllocator, "owns"))
bool owns(void[] b)
{
return b.length <= threshold ? _small.owns(b) : _large.owns(b);
}
static if (hasMember!(SmallAllocator, "deallocate")
&& hasMember!(LargeAllocator, "deallocate"))
void deallocate(void[] data)
{
data.length <= threshold
? _small.deallocate(data)
: _large.deallocate(data);
}
static if (hasMember!(SmallAllocator, "deallocateAll")
&& hasMember!(LargeAllocator, "deallocateAll"))
void deallocateAll()
{
_small.deallocateAll();
_large.deallocateAll();
}
static if (hasMember!(SmallAllocator, "empty")
&& hasMember!(LargeAllocator, "empty"))
bool empty()
{
return _small.empty && _large.empty;
}
static if (hasMember!(SmallAllocator, "resolveInternalPointer")
&& hasMember!(LargeAllocator, "resolveInternalPointer"))
void[] resolveInternalPointer(void* p)
{
if (auto r = _small.resolveInternalPointer(p)) return r;
return _large.resolveInternalPointer(p);
}
static if (hasMember!(SmallAllocator, "markAllAsUnused")
&& hasMember!(LargeAllocator, "markAllAsUnused"))
{
void markAllAsUnused()
{
_small.markAllAsUnused();
_large.markAllAsUnused();
}
bool markAsUsed(void[] b)
{
return b.length <= threshold
? _small.markAsUsed(b)
: _large.markAsUsed(b);
}
void doneMarking()
{
_small.doneMarking();
_large.doneMarking();
}
}
}
enum sharedMethods =
!stateSize!SmallAllocator
&& !stateSize!LargeAllocator
&& is(typeof(SmallAllocator.it) == shared)
&& is(typeof(LargeAllocator.it) == shared);
//pragma(msg, sharedMethods);
static if (sharedMethods)
{
static shared Segregator it;
shared { mixin Impl!(); }
}
else
{
static if (!stateSize!SmallAllocator && !stateSize!LargeAllocator)
static __gshared Segregator it;
mixin Impl!();
}
}
///
unittest
{
import std.experimental.allocator.free_list;
import std.experimental.allocator.mallocator;
import std.experimental.allocator.gc_allocator;
alias A =
Segregator!(
1024 * 4,
Segregator!(
128, FreeList!(Mallocator, 0, 128),
GCAllocator),
Segregator!(
1024 * 1024, Mallocator,
GCAllocator)
);
A a;
auto b = a.allocate(200);
assert(b.length == 200);
a.deallocate(b);
}
/**
A $(D Segregator) with more than three arguments expands to a composition of
elemental $(D Segregator)s, as illustrated by the following example:
----
alias A =
Segregator!(
n1, A1,
n2, A2,
n3, A3,
A4
);
----
With this definition, allocation requests for $(D n1) bytes or less are directed
to $(D A1); requests between $(D n1 + 1) and $(D n2) bytes (inclusive) are
directed to $(D A2); requests between $(D n2 + 1) and $(D n3) bytes (inclusive)
are directed to $(D A3); and requests for more than $(D n3) bytes are directed
to $(D A4). If some particular range should not be handled, $(D NullAllocator)
may be used appropriately.
*/
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 .. $])
//);
}
///
unittest
{
import std.experimental.allocator.free_list;
import std.experimental.allocator.mallocator;
import std.experimental.allocator.gc_allocator;
alias A =
Segregator!(
128, FreeList!(Mallocator, 0, 128),
1024 * 4, GCAllocator,
1024 * 1024, Mallocator,
GCAllocator
);
A a;
auto b = a.allocate(201);
assert(b.length == 201);
a.deallocate(b);
}