| /// | |
| module std.experimental.allocator.building_blocks.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.comparison : min; | |
| import std.traits : hasMember; | |
| 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 $(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 $(D | |
| 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. | |
| */ | |
| Ternary owns(void[] b); | |
| /** | |
| This function is defined only if both allocators define it, and forwards | |
| appropriately depending on $(D b.length). | |
| */ | |
| bool deallocate(void[] b); | |
| /** | |
| This function is defined only if both allocators define it, and calls | |
| $(D deallocateAll) for them in turn. | |
| */ | |
| bool deallocateAll(); | |
| /** | |
| This function is defined only if both allocators define it, and returns | |
| the conjunction of $(D empty) calls for the two. | |
| */ | |
| Ternary 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); | |
| 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 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")) | |
| 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) | |
| static __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 $(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 .. $]) | |
| ); | |
| } | |
| } | |
| /// | |
| @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); | |
| } |