| // Written in the D programming language. | |
| /** | |
| Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/bucketizer.d) | |
| */ | |
| module std.experimental.allocator.building_blocks.bucketizer; | |
| /** | |
| A `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 [max - step + 1, max]). | |
| `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 `min` or larger than `max` are illegal | |
| for `Bucketizer`. To handle them separately, `Segregator` may be of use. | |
| */ | |
| struct Bucketizer(Allocator, size_t min, size_t max, size_t step) | |
| { | |
| import common = std.experimental.allocator.common : roundUpToMultipleOf, | |
| alignedAt; | |
| import std.traits : hasMember; | |
| 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; | |
| pure nothrow @safe @nogc | |
| private Allocator* allocatorFor(size_t n) | |
| { | |
| const i = (n - min) / step; | |
| return i < buckets.length ? &buckets[i] : null; | |
| } | |
| /** | |
| The alignment offered is the same as `Allocator.alignment`. | |
| */ | |
| enum uint alignment = Allocator.alignment; | |
| /** | |
| Rounds up to the maximum size of the bucket in which `bytes` falls. | |
| */ | |
| pure nothrow @safe @nogc | |
| 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 `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; | |
| } | |
| static if (hasMember!(Allocator, "allocateZeroed")) | |
| package(std) void[] allocateZeroed()(size_t bytes) | |
| { | |
| if (!bytes) return null; | |
| if (auto a = allocatorFor(bytes)) | |
| { | |
| const actual = goodAllocSize(bytes); | |
| auto result = a.allocateZeroed(actual); | |
| return result.ptr ? result.ptr[0 .. bytes] : null; | |
| } | |
| return null; | |
| } | |
| /** | |
| Allocates the requested `bytes` of memory with specified `alignment`. | |
| Directs the call to either one of the `buckets` allocators. Defined only | |
| if `Allocator` defines `alignedAllocate`. | |
| */ | |
| static if (hasMember!(Allocator, "alignedAllocate")) | |
| void[] alignedAllocate(size_t bytes, uint alignment) | |
| { | |
| if (!bytes) return null; | |
| if (auto a = allocatorFor(bytes)) | |
| { | |
| const actual = goodAllocSize(bytes); | |
| auto result = a.alignedAllocate(actual, alignment); | |
| return result !is null ? (() @trusted => (&result[0])[0 .. bytes])() : null; | |
| } | |
| return null; | |
| } | |
| /** | |
| This method allows expansion within the respective bucket range. It succeeds | |
| if both `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 || delta == 0) return delta == 0; | |
| assert(b.length >= min && b.length <= max); | |
| const available = goodAllocSize(b.length); | |
| const desired = b.length + delta; | |
| if (available < desired) return false; | |
| b = (() @trusted => b.ptr[0 .. desired])(); | |
| return true; | |
| } | |
| /** | |
| This method allows reallocation within the respective bucket range. If both | |
| `b.length` and `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 && expand(b, size - b.length)) | |
| { | |
| return true; | |
| } | |
| 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 && b.ptr.alignedAt(a) && expand(b, size - b.length)) | |
| { | |
| return true; | |
| } | |
| assert(b.length >= min && b.length <= max); | |
| if (goodAllocSize(size) == goodAllocSize(b.length) && b.ptr.alignedAt(a)) | |
| { | |
| b = b.ptr[0 .. size]; | |
| return true; | |
| } | |
| // Move cross buckets | |
| return common.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 `Allocator` defines `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")) | |
| Ternary resolveInternalPointer(const void* p, ref void[] result) | |
| { | |
| foreach (ref a; buckets) | |
| { | |
| Ternary r = a.resolveInternalPointer(p, result); | |
| if (r == Ternary.yes) return r; | |
| } | |
| return Ternary.no; | |
| } | |
| } | |
| /// | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : max; | |
| 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.common : unbounded; | |
| import std.experimental.allocator.mallocator : Mallocator; | |
| import std.typecons : Ternary; | |
| 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); | |
| } | |
| @system unittest | |
| { | |
| import std.algorithm.comparison : max; | |
| 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.common : unbounded; | |
| import std.experimental.allocator.mallocator : Mallocator; | |
| import std.typecons : Ternary; | |
| Bucketizer!( | |
| FreeList!( | |
| AllocatorList!( | |
| (size_t n) => Region!Mallocator(max(n, 1024 * 1024)), Mallocator), | |
| 0, unbounded), | |
| 65, 512, 64) a; | |
| assert((() pure nothrow @safe @nogc => a.goodAllocSize(65))() == 128); | |
| auto b = a.allocate(100); | |
| assert(b.length == 100); | |
| // Make reallocate use extend | |
| assert((() nothrow @nogc => a.reallocate(b, 101))()); | |
| assert(b.length == 101); | |
| // Move cross buckets | |
| assert((() nothrow @nogc => a.reallocate(b, 200))()); | |
| assert(b.length == 200); | |
| // Free through realloc | |
| assert((() nothrow @nogc => a.reallocate(b, 0))()); | |
| assert(b is null); | |
| // Ensure deallocate inherits from parent allocators | |
| assert((() nothrow @nogc => a.deallocate(b))()); | |
| assert((() nothrow @nogc => a.deallocateAll())()); | |
| } | |
| // Test alignedAllocate | |
| @system unittest | |
| { | |
| import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock; | |
| import std.experimental.allocator.gc_allocator : GCAllocator; | |
| Bucketizer!(BitmappedBlock!(64, 8, GCAllocator), 65, 512, 64) a; | |
| foreach (ref bucket; a.buckets) | |
| { | |
| bucket = BitmappedBlock!(64, 8, GCAllocator)(new ubyte[1024]); | |
| } | |
| auto b = a.alignedAllocate(100, 16); | |
| assert(b.length == 100); | |
| assert(a.alignedAllocate(42, 16) is null); | |
| assert(a.alignedAllocate(0, 16) is null); | |
| assert((() pure nothrow @safe @nogc => a.expand(b, 0))()); | |
| assert(b.length == 100); | |
| assert((() pure nothrow @safe @nogc => a.expand(b, 28))()); | |
| assert(b.length == 128); | |
| assert((() pure nothrow @safe @nogc => !a.expand(b, 1))()); | |
| } | |
| @system unittest | |
| { | |
| import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock; | |
| import std.experimental.allocator.gc_allocator : GCAllocator; | |
| Bucketizer!(BitmappedBlock!(64, 8, GCAllocator), 1, 512, 64) a; | |
| foreach (ref bucket; a.buckets) | |
| { | |
| bucket = BitmappedBlock!(64, 8, GCAllocator)(new ubyte[1024]); | |
| } | |
| auto b = a.alignedAllocate(1, 4); | |
| assert(b.length == 1); | |
| // Make reallocate use extend | |
| assert(a.alignedReallocate(b, 11, 4)); | |
| assert(b.length == 11); | |
| // Make reallocate use use realloc because of alignment change | |
| assert(a.alignedReallocate(b, 21, 16)); | |
| assert(b.length == 21); | |
| // Make reallocate use extend | |
| assert(a.alignedReallocate(b, 22, 16)); | |
| assert(b.length == 22); | |
| // Move cross buckets | |
| assert(a.alignedReallocate(b, 101, 16)); | |
| assert(b.length == 101); | |
| // Free through realloc | |
| assert(a.alignedReallocate(b, 0, 16)); | |
| assert(b is null); | |
| } |