phobos/std/experimental/allocator/building_blocks/bucketizer.d
2016-05-27 16:05:35 -04:00

242 lines
7.2 KiB
D

module std.experimental.allocator.building_blocks.bucketizer;
/**
A $(D Bucketizer) uses distinct allocators for handling allocations of sizes in
the intervals $(D [min, min + step - 1]), $(D [min + step, min + 2 * step - 1]),
$(D [min + 2 * step, min + 3 * step - 1]), $(D ...), $(D [max - step + 1, max]).
$(D Bucketizer) holds a fixed-size array of allocators and dispatches calls to
them appropriately. The size of the array is $(D (max + 1 - min) / step), which
must be an exact division.
Allocations for sizes smaller than $(D min) or larger than $(D max) are illegal
for $(D Bucketizer). To handle them separately, $(D Segregator) may be of use.
*/
struct Bucketizer(Allocator, size_t min, size_t max, size_t step)
{
import std.traits : hasMember;
import common = std.experimental.allocator.common : roundUpToMultipleOf;
import std.typecons : Ternary;
static assert((max - (min - 1)) % step == 0,
"Invalid limits when instantiating " ~ Bucketizer.stringof);
// state
/**
The array of allocators is publicly available for e.g. initialization and
inspection.
*/
Allocator[(max + 1 - min) / step] buckets;
private Allocator* allocatorFor(size_t n)
{
const i = (n - min) / step;
return i < buckets.length ? buckets.ptr + i : null;
}
/**
The alignment offered is the same as $(D Allocator.alignment).
*/
enum uint alignment = Allocator.alignment;
/**
Rounds up to the maximum size of the bucket in which $(D bytes) falls.
*/
size_t goodAllocSize(size_t bytes) const
{
// round up bytes such that bytes - min + 1 is a multiple of step
assert(bytes >= min);
const min_1 = min - 1;
return min_1 + roundUpToMultipleOf(bytes - min_1, step);
}
/**
Directs the call to either one of the $(D buckets) allocators.
*/
void[] allocate(size_t bytes)
{
if (!bytes) return null;
if (auto a = allocatorFor(bytes))
{
const actual = goodAllocSize(bytes);
auto result = a.allocate(actual);
return result.ptr ? result.ptr[0 .. bytes] : null;
}
return null;
}
/**
Directs the call to either one of the $(D buckets) allocators. Defined only
if `Allocator` defines `alignedAllocate`.
*/
static if (hasMember!(Allocator, "alignedAllocate"))
void[] alignedAllocate(size_t bytes, uint a)
{
if (!bytes) return null;
if (auto a = allocatorFor(b.length))
{
const actual = goodAllocSize(bytes);
auto result = a.alignedAllocate(actual);
return result.ptr ? result.ptr[0 .. bytes] : null;
}
return null;
}
/**
This method allows expansion within the respective bucket range. It succeeds
if both $(D b.length) and $(D b.length + delta) fall in a range of the form
$(D [min + k * step, min + (k + 1) * step - 1]).
*/
bool expand(ref void[] b, size_t delta)
{
if (!b.ptr)
{
b = allocate(delta);
return b.length == delta;
}
assert(b.length >= min && b.length <= max);
const available = goodAllocSize(b.length);
const desired = b.length + delta;
if (available < desired) return false;
b = b.ptr[0 .. desired];
return true;
}
/**
This method allows reallocation within the respective bucket range. If both
$(D b.length) and $(D size) fall in a range of the form $(D [min + k *
step, min + (k + 1) * step - 1]), then reallocation is in place. Otherwise,
reallocation with moving is attempted.
*/
bool reallocate(ref void[] b, size_t size)
{
if (size == 0)
{
deallocate(b);
b = null;
return true;
}
if (size >= b.length)
{
return expand(b, size - b.length);
}
assert(b.length >= min && b.length <= max);
if (goodAllocSize(size) == goodAllocSize(b.length))
{
b = b.ptr[0 .. size];
return true;
}
// Move cross buckets
return common.reallocate(this, b, size);
}
/**
Similar to `reallocate`, with alignment. Defined only if `Allocator`
defines `alignedReallocate`.
*/
static if (hasMember!(Allocator, "alignedReallocate"))
bool alignedReallocate(ref void[] b, size_t size, uint a)
{
if (size == 0)
{
deallocate(b);
b = null;
return true;
}
if (size >= b.length)
{
return expand(b, size - b.length);
}
assert(b.length >= min && b.length <= max);
if (goodAllocSize(size) == goodAllocSize(b.length))
{
b = b.ptr[0 .. size];
return true;
}
// Move cross buckets
return .alignedReallocate(this, b, size, a);
}
/**
Defined only if `Allocator` defines `owns`. Finds the owner of `b` and forwards the call to it.
*/
static if (hasMember!(Allocator, "owns"))
Ternary owns(void[] b)
{
if (!b.ptr) return Ternary.no;
if (auto a = allocatorFor(b.length))
{
const actual = goodAllocSize(b.length);
return a.owns(b.ptr[0 .. actual]);
}
return Ternary.no;
}
/**
This method is only defined if $(D Allocator) defines $(D deallocate).
*/
static if (hasMember!(Allocator, "deallocate"))
bool deallocate(void[] b)
{
if (!b.ptr) return true;
if (auto a = allocatorFor(b.length))
{
a.deallocate(b.ptr[0 .. goodAllocSize(b.length)]);
}
return true;
}
/**
This method is only defined if all allocators involved define $(D
deallocateAll), and calls it for each bucket in turn. Returns `true` if all
allocators could deallocate all.
*/
static if (hasMember!(Allocator, "deallocateAll"))
bool deallocateAll()
{
bool result = true;
foreach (ref a; buckets)
{
if (!a.deallocateAll()) result = false;
}
return result;
}
/**
This method is only defined if all allocators involved define $(D
resolveInternalPointer), and tries it for each bucket in turn.
*/
static if (hasMember!(Allocator, "resolveInternalPointer"))
void[] resolveInternalPointer(void* p)
{
foreach (ref a; buckets)
{
if (auto r = a.resolveInternalPointer(p)) return r;
}
return null;
}
}
///
unittest
{
import std.experimental.allocator.building_blocks.allocator_list : AllocatorList;
import std.experimental.allocator.building_blocks.free_list : FreeList;
import std.experimental.allocator.building_blocks.region : Region;
import std.experimental.allocator.mallocator : Mallocator;
import std.experimental.allocator.common : unbounded;
import std.typecons : Ternary;
import std.algorithm : max;
Bucketizer!(
FreeList!(
AllocatorList!(
(size_t n) => Region!Mallocator(max(n, 1024 * 1024))),
0, unbounded),
65, 512, 64) a;
auto b = a.allocate(400);
assert(b.length == 400);
assert(a.owns(b) == Ternary.yes);
a.deallocate(b);
}