mirror of
https://github.com/dlang/phobos.git
synced 2025-04-30 23:20:29 +03:00

Region will now always assume that its ParentAllocator is a real allocator, and attempt to use its 'allocate' and 'deallocate' methods accordingly. The behavior previously provided by Region!(NullAllocator, ...) remains available via BorrowedRegion!(...). See issue 23090 for detailed rationale.
520 lines
18 KiB
D
520 lines
18 KiB
D
// Written in the D programming language.
|
|
/**
|
|
Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/segregator.d)
|
|
*/
|
|
module std.experimental.allocator.building_blocks.segregator;
|
|
|
|
import std.experimental.allocator.common;
|
|
|
|
/**
|
|
Dispatches allocations (and deallocations) between two allocators ($(D
|
|
SmallAllocator) and `LargeAllocator`) depending on the size allocated, as
|
|
follows. All allocations smaller than or equal to `threshold` will be
|
|
dispatched to `SmallAllocator`. The others will go to `LargeAllocator`.
|
|
|
|
If both allocators are `shared`, the `Segregator` will also offer $(D
|
|
shared) methods.
|
|
*/
|
|
struct Segregator(size_t threshold, SmallAllocator, LargeAllocator)
|
|
{
|
|
import std.algorithm.comparison : min;
|
|
import std.traits : hasMember, ReturnType;
|
|
import std.typecons : Ternary;
|
|
|
|
static if (stateSize!SmallAllocator) private SmallAllocator _small;
|
|
else private alias _small = SmallAllocator.instance;
|
|
static if (stateSize!LargeAllocator) private LargeAllocator _large;
|
|
else private alias _large = LargeAllocator.instance;
|
|
|
|
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 `SmallAllocator` if $(D
|
|
s <= threshold), or `LargeAllocator` otherwise. (If one of the
|
|
allocators does not define `goodAllocSize`, the default
|
|
implementation in this module applies.)
|
|
*/
|
|
static size_t goodAllocSize(size_t s);
|
|
/**
|
|
The memory is obtained from `SmallAllocator` if $(D s <= threshold),
|
|
or `LargeAllocator` otherwise.
|
|
*/
|
|
void[] allocate(size_t);
|
|
/**
|
|
This method is defined if both allocators define it, and forwards to
|
|
`SmallAllocator` or `LargeAllocator` appropriately.
|
|
*/
|
|
void[] alignedAllocate(size_t, uint);
|
|
/**
|
|
This method is defined only if at least one of the allocators defines
|
|
it. If `SmallAllocator` defines `expand` and $(D b.length +
|
|
delta <= threshold), the call is forwarded to `SmallAllocator`. If $(D
|
|
LargeAllocator) defines `expand` and $(D b.length > threshold), the
|
|
call is forwarded to `LargeAllocator`. Otherwise, the call returns
|
|
`false`.
|
|
*/
|
|
bool expand(ref void[] b, size_t delta);
|
|
/**
|
|
This method is defined only if at least one of the allocators defines
|
|
it. If `SmallAllocator` defines `reallocate` and $(D b.length <=
|
|
threshold && s <= threshold), the call is forwarded to $(D
|
|
SmallAllocator). If `LargeAllocator` defines `expand` and $(D
|
|
b.length > threshold && s > threshold), the call is forwarded to $(D
|
|
LargeAllocator). Otherwise, the call returns `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 `reallocate`.
|
|
*/
|
|
bool alignedReallocate(ref void[] b, size_t s, uint a);
|
|
/**
|
|
This method is defined only if both allocators define it. The call is
|
|
forwarded to `SmallAllocator` if $(D b.length <= threshold), or $(D
|
|
LargeAllocator) otherwise.
|
|
*/
|
|
Ternary owns(void[] b);
|
|
/**
|
|
This function is defined only if both allocators define it, and forwards
|
|
appropriately depending on `b.length`.
|
|
*/
|
|
bool deallocate(void[] b);
|
|
/**
|
|
This function is defined only if both allocators define it, and calls
|
|
`deallocateAll` for them in turn.
|
|
*/
|
|
bool deallocateAll();
|
|
/**
|
|
This function is defined only if both allocators define it, and returns
|
|
the conjunction of `empty` calls for the two.
|
|
*/
|
|
Ternary empty();
|
|
}
|
|
|
|
/**
|
|
Composite allocators involving nested instantiations of `Segregator` make
|
|
it difficult to access individual sub-allocators stored within. $(D
|
|
allocatorForSize) simplifies the task by supplying the allocator nested
|
|
inside a `Segregator` that is responsible for a specific size `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);
|
|
|
|
private 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 (!delta) return true;
|
|
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 alignedReallocate(ref void[] b, size_t s, uint a)
|
|
{
|
|
static if (hasMember!(SmallAllocator, "alignedReallocate"))
|
|
if (b.length <= threshold && s <= threshold)
|
|
{
|
|
// Old and new allocations handled by _small
|
|
return _small.alignedReallocate(b, s, a);
|
|
}
|
|
static if (hasMember!(LargeAllocator, "alignedReallocate"))
|
|
if (b.length > threshold && s > threshold)
|
|
{
|
|
// Old and new allocations handled by _large
|
|
return _large.alignedReallocate(b, s, a);
|
|
}
|
|
// Cross-allocator transgression
|
|
return .alignedReallocate(this, b, s, a);
|
|
}
|
|
|
|
static if (hasMember!(SmallAllocator, "allocateZeroed")
|
|
|| hasMember!(LargeAllocator, "allocateZeroed"))
|
|
package(std) void[] allocateZeroed()(size_t s)
|
|
{
|
|
if (s <= threshold)
|
|
{
|
|
static if (hasMember!(SmallAllocator, "allocateZeroed"))
|
|
return _small.allocateZeroed(s);
|
|
else
|
|
{
|
|
auto b = _small.allocate(s);
|
|
(() @trusted => (cast(ubyte[]) b)[] = 0)(); // OK even if b is null.
|
|
return b;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
static if (hasMember!(LargeAllocator, "allocateZeroed"))
|
|
return _large.allocateZeroed(s);
|
|
else
|
|
{
|
|
auto b = _large.allocate(s);
|
|
(() @trusted => (cast(ubyte[]) b)[] = 0)(); // OK even if b is null.
|
|
return b;
|
|
}
|
|
}
|
|
}
|
|
|
|
static if (hasMember!(SmallAllocator, "owns")
|
|
&& hasMember!(LargeAllocator, "owns"))
|
|
Ternary owns(void[] b)
|
|
{
|
|
return Ternary(b.length <= threshold
|
|
? _small.owns(b) : _large.owns(b));
|
|
}
|
|
|
|
static if (hasMember!(SmallAllocator, "deallocate")
|
|
&& hasMember!(LargeAllocator, "deallocate"))
|
|
bool deallocate(void[] data)
|
|
{
|
|
return data.length <= threshold
|
|
? _small.deallocate(data)
|
|
: _large.deallocate(data);
|
|
}
|
|
|
|
static if (hasMember!(SmallAllocator, "deallocateAll")
|
|
&& hasMember!(LargeAllocator, "deallocateAll"))
|
|
bool deallocateAll()
|
|
{
|
|
// Use & insted of && to evaluate both
|
|
return _small.deallocateAll() & _large.deallocateAll();
|
|
}
|
|
|
|
static if (hasMember!(SmallAllocator, "empty")
|
|
&& hasMember!(LargeAllocator, "empty"))
|
|
Ternary empty()
|
|
{
|
|
return _small.empty & _large.empty;
|
|
}
|
|
|
|
static if (hasMember!(SmallAllocator, "resolveInternalPointer")
|
|
&& hasMember!(LargeAllocator, "resolveInternalPointer"))
|
|
Ternary resolveInternalPointer(const void* p, ref void[] result)
|
|
{
|
|
Ternary r = _small.resolveInternalPointer(p, result);
|
|
return r == Ternary.no ? _large.resolveInternalPointer(p, result) : r;
|
|
}
|
|
}
|
|
|
|
private enum sharedMethods =
|
|
!stateSize!SmallAllocator
|
|
&& !stateSize!LargeAllocator
|
|
&& is(typeof(SmallAllocator.instance) == shared)
|
|
&& is(typeof(LargeAllocator.instance) == shared);
|
|
|
|
static if (sharedMethods)
|
|
{
|
|
static shared Segregator instance;
|
|
shared { mixin Impl!(); }
|
|
}
|
|
else
|
|
{
|
|
static if (!stateSize!SmallAllocator && !stateSize!LargeAllocator)
|
|
__gshared Segregator instance;
|
|
mixin Impl!();
|
|
}
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.experimental.allocator.building_blocks.free_list : FreeList;
|
|
import std.experimental.allocator.gc_allocator : GCAllocator;
|
|
import std.experimental.allocator.mallocator : Mallocator;
|
|
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 `Segregator` with more than three arguments expands to a composition of
|
|
elemental `Segregator`s, as illustrated by the following example:
|
|
|
|
----
|
|
alias A =
|
|
Segregator!(
|
|
n1, A1,
|
|
n2, A2,
|
|
n3, A3,
|
|
A4
|
|
);
|
|
----
|
|
|
|
With this definition, allocation requests for `n1` bytes or less are directed
|
|
to `A1`; requests between $(D n1 + 1) and `n2` bytes (inclusive) are
|
|
directed to `A2`; requests between $(D n2 + 1) and `n3` bytes (inclusive)
|
|
are directed to `A3`; and requests for more than `n3` bytes are directed
|
|
to `A4`. If some particular range should not be handled, `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 .. $])
|
|
);
|
|
}
|
|
}
|
|
|
|
///
|
|
@system unittest
|
|
{
|
|
import std.experimental.allocator.building_blocks.free_list : FreeList;
|
|
import std.experimental.allocator.gc_allocator : GCAllocator;
|
|
import std.experimental.allocator.mallocator : Mallocator;
|
|
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);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.experimental.allocator.gc_allocator : GCAllocator;
|
|
import std.experimental.allocator.building_blocks.kernighan_ritchie : KRRegion;
|
|
Segregator!(128, GCAllocator, KRRegion!GCAllocator) alloc;
|
|
assert((() nothrow @safe @nogc => alloc.goodAllocSize(1))()
|
|
== GCAllocator.instance.goodAllocSize(1));
|
|
|
|
// Note: we infer `shared` from GCAllocator.goodAllocSize so we need a
|
|
// shared object in order to be able to use the function
|
|
shared Segregator!(128, GCAllocator, GCAllocator) sharedAlloc;
|
|
assert((() nothrow @safe @nogc => sharedAlloc.goodAllocSize(1))()
|
|
== GCAllocator.instance.goodAllocSize(1));
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock;
|
|
import std.typecons : Ternary;
|
|
|
|
alias A =
|
|
Segregator!(
|
|
128, BitmappedBlock!(4096),
|
|
BitmappedBlock!(4096)
|
|
);
|
|
|
|
A a = A(
|
|
BitmappedBlock!(4096)(new ubyte[4096 * 1024]),
|
|
BitmappedBlock!(4096)(new ubyte[4096 * 1024])
|
|
);
|
|
|
|
assert(a.empty == Ternary.yes);
|
|
auto b = a.allocate(42);
|
|
assert(b.length == 42);
|
|
assert(a.empty == Ternary.no);
|
|
assert(a.alignedReallocate(b, 256, 512));
|
|
assert(b.length == 256);
|
|
assert(a.alignedReallocate(b, 42, 512));
|
|
assert(b.length == 42);
|
|
assert((() pure nothrow @safe @nogc => a.owns(b))() == Ternary.yes);
|
|
assert((() pure nothrow @safe @nogc => a.owns(null))() == Ternary.no);
|
|
// Ensure deallocate inherits from parent allocators
|
|
assert((() nothrow @nogc => a.deallocate(b))());
|
|
assert(a.empty == Ternary.yes);
|
|
|
|
// Test that deallocateAll inherits from parents
|
|
auto c = a.allocate(42);
|
|
assert(c.length == 42);
|
|
assert((() pure nothrow @safe @nogc => a.expand(c, 58))());
|
|
assert(c.length == 100);
|
|
assert(a.empty == Ternary.no);
|
|
assert((() nothrow @nogc => a.deallocateAll())());
|
|
assert(a.empty == Ternary.yes);
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.experimental.allocator.gc_allocator : GCAllocator;
|
|
import std.typecons : Ternary;
|
|
|
|
shared Segregator!(1024 * 4, GCAllocator, GCAllocator) a;
|
|
|
|
auto b = a.allocate(201);
|
|
assert(b.length == 201);
|
|
|
|
void[] p;
|
|
assert((() nothrow @safe @nogc => a.resolveInternalPointer(&b[0], p))() == Ternary.yes);
|
|
assert((() nothrow @safe @nogc => a.resolveInternalPointer(null, p))() == Ternary.no);
|
|
|
|
// Ensure deallocate inherits from parent allocators
|
|
assert((() nothrow @nogc => a.deallocate(b))());
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlockWithInternalPointers;
|
|
import std.typecons : Ternary;
|
|
|
|
alias A =
|
|
Segregator!(
|
|
10_240, BitmappedBlockWithInternalPointers!(4096),
|
|
BitmappedBlockWithInternalPointers!(4096)
|
|
);
|
|
|
|
A a = A(
|
|
BitmappedBlockWithInternalPointers!(4096)(new ubyte[4096 * 1024]),
|
|
BitmappedBlockWithInternalPointers!(4096)(new ubyte[4096 * 1024])
|
|
);
|
|
|
|
assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes);
|
|
auto b = a.allocate(201);
|
|
assert(b.length == 201);
|
|
assert((() nothrow @safe @nogc => a.empty)() == Ternary.no);
|
|
assert((() nothrow @nogc => a.deallocate(b))());
|
|
}
|
|
|
|
// Test that reallocate infers from parent
|
|
@system unittest
|
|
{
|
|
import std.experimental.allocator.mallocator : Mallocator;
|
|
|
|
alias a = Segregator!(10_240, Mallocator, Mallocator).instance;
|
|
|
|
auto b = a.allocate(42);
|
|
assert(b.length == 42);
|
|
assert((() nothrow @nogc => a.reallocate(b, 100))());
|
|
assert(b.length == 100);
|
|
assert((() nothrow @nogc => a.deallocate(b))());
|
|
}
|
|
|
|
@system unittest
|
|
{
|
|
import std.experimental.allocator.building_blocks.region : BorrowedRegion;
|
|
import std.typecons : Ternary;
|
|
|
|
auto a = Segregator!(10_240, BorrowedRegion!(), BorrowedRegion!())(
|
|
BorrowedRegion!()(new ubyte[4096 * 1024]),
|
|
BorrowedRegion!()(new ubyte[4096 * 1024]));
|
|
|
|
assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes);
|
|
auto b = a.alignedAllocate(42, 8);
|
|
assert(b.length == 42);
|
|
assert((() nothrow @nogc => a.alignedReallocate(b, 100, 8))());
|
|
assert(b.length == 100);
|
|
assert((() nothrow @safe @nogc => a.empty)() == Ternary.no);
|
|
assert((() nothrow @nogc => a.deallocate(b))());
|
|
}
|