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