| // 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))()); |
| } |