1 /// 2 module stdx.allocator.building_blocks.bucketizer; 3 4 /** 5 6 A $(D Bucketizer) uses distinct allocators for handling allocations of sizes in 7 the intervals $(D [min, min + step - 1]), $(D [min + step, min + 2 * step - 1]), 8 $(D [min + 2 * step, min + 3 * step - 1]), $(D ...), $(D [max - step + 1, max]). 9 10 $(D Bucketizer) holds a fixed-size array of allocators and dispatches calls to 11 them appropriately. The size of the array is $(D (max + 1 - min) / step), which 12 must be an exact division. 13 14 Allocations for sizes smaller than $(D min) or larger than $(D max) are illegal 15 for $(D Bucketizer). To handle them separately, $(D Segregator) may be of use. 16 17 */ 18 struct Bucketizer(Allocator, size_t min, size_t max, size_t step) 19 { 20 import common = stdx.allocator.common : roundUpToMultipleOf; 21 import stdx.allocator.internal : Ternary; 22 23 static assert((max - (min - 1)) % step == 0, 24 "Invalid limits when instantiating " ~ Bucketizer.stringof); 25 26 // state 27 /** 28 The array of allocators is publicly available for e.g. initialization and 29 inspection. 30 */ 31 Allocator[(max + 1 - min) / step] buckets; 32 33 private Allocator* allocatorFor(size_t n) 34 { 35 const i = (n - min) / step; 36 return i < buckets.length ? buckets.ptr + i : null; 37 } 38 39 /** 40 The alignment offered is the same as $(D Allocator.alignment). 41 */ 42 enum uint alignment = Allocator.alignment; 43 44 /** 45 Rounds up to the maximum size of the bucket in which $(D bytes) falls. 46 */ 47 size_t goodAllocSize(size_t bytes) const 48 { 49 // round up bytes such that bytes - min + 1 is a multiple of step 50 assert(bytes >= min); 51 const min_1 = min - 1; 52 return min_1 + roundUpToMultipleOf(bytes - min_1, step); 53 } 54 55 /** 56 Directs the call to either one of the $(D buckets) allocators. 57 */ 58 void[] allocate(size_t bytes) 59 { 60 if (!bytes) return null; 61 if (auto a = allocatorFor(bytes)) 62 { 63 const actual = goodAllocSize(bytes); 64 auto result = a.allocate(actual); 65 return result.ptr ? result.ptr[0 .. bytes] : null; 66 } 67 return null; 68 } 69 70 /** 71 Directs the call to either one of the $(D buckets) allocators. Defined only 72 if `Allocator` defines `alignedAllocate`. 73 */ 74 static if (__traits(hasMember, Allocator, "alignedAllocate")) 75 void[] alignedAllocate(size_t bytes, uint a) 76 { 77 if (!bytes) return null; 78 if (auto a = allocatorFor(b.length)) 79 { 80 const actual = goodAllocSize(bytes); 81 auto result = a.alignedAllocate(actual); 82 return result.ptr ? result.ptr[0 .. bytes] : null; 83 } 84 return null; 85 } 86 87 /** 88 This method allows expansion within the respective bucket range. It succeeds 89 if both $(D b.length) and $(D b.length + delta) fall in a range of the form 90 $(D [min + k * step, min + (k + 1) * step - 1]). 91 */ 92 bool expand(ref void[] b, size_t delta) 93 { 94 if (!b.ptr) return delta == 0; 95 assert(b.length >= min && b.length <= max); 96 const available = goodAllocSize(b.length); 97 const desired = b.length + delta; 98 if (available < desired) return false; 99 b = b.ptr[0 .. desired]; 100 return true; 101 } 102 103 /** 104 This method allows reallocation within the respective bucket range. If both 105 $(D b.length) and $(D size) fall in a range of the form $(D [min + k * 106 step, min + (k + 1) * step - 1]), then reallocation is in place. Otherwise, 107 reallocation with moving is attempted. 108 */ 109 bool reallocate(ref void[] b, size_t size) 110 { 111 if (size == 0) 112 { 113 deallocate(b); 114 b = null; 115 return true; 116 } 117 if (size >= b.length) 118 { 119 return expand(b, size - b.length); 120 } 121 assert(b.length >= min && b.length <= max); 122 if (goodAllocSize(size) == goodAllocSize(b.length)) 123 { 124 b = b.ptr[0 .. size]; 125 return true; 126 } 127 // Move cross buckets 128 return common.reallocate(this, b, size); 129 } 130 131 /** 132 Similar to `reallocate`, with alignment. Defined only if `Allocator` 133 defines `alignedReallocate`. 134 */ 135 static if (__traits(hasMember, Allocator, "alignedReallocate")) 136 bool alignedReallocate(ref void[] b, size_t size, uint a) 137 { 138 if (size == 0) 139 { 140 deallocate(b); 141 b = null; 142 return true; 143 } 144 if (size >= b.length) 145 { 146 return expand(b, size - b.length); 147 } 148 assert(b.length >= min && b.length <= max); 149 if (goodAllocSize(size) == goodAllocSize(b.length)) 150 { 151 b = b.ptr[0 .. size]; 152 return true; 153 } 154 // Move cross buckets 155 return .alignedReallocate(this, b, size, a); 156 } 157 158 /** 159 Defined only if `Allocator` defines `owns`. Finds the owner of `b` and forwards the call to it. 160 */ 161 static if (__traits(hasMember, Allocator, "owns")) 162 Ternary owns(void[] b) 163 { 164 if (!b.ptr) return Ternary.no; 165 if (auto a = allocatorFor(b.length)) 166 { 167 const actual = goodAllocSize(b.length); 168 return a.owns(b.ptr[0 .. actual]); 169 } 170 return Ternary.no; 171 } 172 173 /** 174 This method is only defined if $(D Allocator) defines $(D deallocate). 175 */ 176 static if (__traits(hasMember, Allocator, "deallocate")) 177 bool deallocate(void[] b) 178 { 179 if (!b.ptr) return true; 180 if (auto a = allocatorFor(b.length)) 181 { 182 a.deallocate(b.ptr[0 .. goodAllocSize(b.length)]); 183 } 184 return true; 185 } 186 187 /** 188 This method is only defined if all allocators involved define $(D 189 deallocateAll), and calls it for each bucket in turn. Returns `true` if all 190 allocators could deallocate all. 191 */ 192 static if (__traits(hasMember, Allocator, "deallocateAll")) 193 bool deallocateAll() 194 { 195 bool result = true; 196 foreach (ref a; buckets) 197 { 198 if (!a.deallocateAll()) result = false; 199 } 200 return result; 201 } 202 203 /** 204 This method is only defined if all allocators involved define $(D 205 resolveInternalPointer), and tries it for each bucket in turn. 206 */ 207 static if (__traits(hasMember, Allocator, "resolveInternalPointer")) 208 Ternary resolveInternalPointer(const void* p, ref void[] result) 209 { 210 foreach (ref a; buckets) 211 { 212 Ternary r = a.resolveInternalPointer(p, result); 213 if (r == Ternary.yes) return r; 214 } 215 return Ternary.no; 216 } 217 } 218 219 /// 220 @system unittest 221 { 222 import mir.utility : max; 223 import stdx.allocator.building_blocks.allocator_list : AllocatorList; 224 import stdx.allocator.building_blocks.free_list : FreeList; 225 import stdx.allocator.building_blocks.region : Region; 226 import stdx.allocator.common : unbounded; 227 import stdx.allocator.mallocator : Mallocator; 228 import stdx.allocator.internal : Ternary; 229 Bucketizer!( 230 FreeList!( 231 AllocatorList!( 232 (size_t n) => Region!Mallocator(max(n, 1024u * 1024))), 233 0, unbounded), 234 65, 512, 64) a; 235 auto b = a.allocate(400); 236 assert(b.length == 400); 237 assert(a.owns(b) == Ternary.yes); 238 void[] p; 239 a.deallocate(b); 240 }