1 // Written in the D programming language. 2 /** 3 Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/bucketizer.d) 4 */ 5 module std.experimental.allocator.building_blocks.bucketizer; 6 7 /** 8 9 A `Bucketizer` uses distinct allocators for handling allocations of sizes in 10 the intervals $(D [min, min + step - 1]), $(D [min + step, min + 2 * step - 1]), 11 $(D [min + 2 * step, min + 3 * step - 1]), `...`, $(D [max - step + 1, max]). 12 13 `Bucketizer` holds a fixed-size array of allocators and dispatches calls to 14 them appropriately. The size of the array is $(D (max + 1 - min) / step), which 15 must be an exact division. 16 17 Allocations for sizes smaller than `min` or larger than `max` are illegal 18 for `Bucketizer`. To handle them separately, `Segregator` may be of use. 19 20 */ 21 struct Bucketizer(Allocator, size_t min, size_t max, size_t step) 22 { 23 import common = std.experimental.allocator.common : roundUpToMultipleOf, 24 alignedAt; 25 import std.traits : hasMember; 26 import std.typecons : Ternary; 27 28 static assert((max - (min - 1)) % step == 0, 29 "Invalid limits when instantiating " ~ Bucketizer.stringof); 30 31 // state 32 /** 33 The array of allocators is publicly available for e.g. initialization and 34 inspection. 35 */ 36 Allocator[(max + 1 - min) / step] buckets; 37 38 pure nothrow @safe @nogc 39 private Allocator* allocatorFor(size_t n) 40 { 41 const i = (n - min) / step; 42 return i < buckets.length ? &buckets[i] : null; 43 } 44 45 /** 46 The alignment offered is the same as `Allocator.alignment`. 47 */ 48 enum uint alignment = Allocator.alignment; 49 50 /** 51 Rounds up to the maximum size of the bucket in which `bytes` falls. 52 */ 53 pure nothrow @safe @nogc 54 size_t goodAllocSize(size_t bytes) const 55 { 56 // round up bytes such that bytes - min + 1 is a multiple of step 57 assert(bytes >= min); 58 const min_1 = min - 1; 59 return min_1 + roundUpToMultipleOf(bytes - min_1, step); 60 } 61 62 /** 63 Directs the call to either one of the `buckets` allocators. 64 */ 65 void[] allocate(size_t bytes) 66 { 67 if (!bytes) return null; 68 if (auto a = allocatorFor(bytes)) 69 { 70 const actual = goodAllocSize(bytes); 71 auto result = a.allocate(actual); 72 return result.ptr ? result.ptr[0 .. bytes] : null; 73 } 74 return null; 75 } 76 77 static if (hasMember!(Allocator, "allocateZeroed")) 78 package(std) void[] allocateZeroed()(size_t bytes) 79 { 80 if (!bytes) return null; 81 if (auto a = allocatorFor(bytes)) 82 { 83 const actual = goodAllocSize(bytes); 84 auto result = a.allocateZeroed(actual); 85 return result.ptr ? result.ptr[0 .. bytes] : null; 86 } 87 return null; 88 } 89 90 /** 91 Allocates the requested `bytes` of memory with specified `alignment`. 92 Directs the call to either one of the `buckets` allocators. Defined only 93 if `Allocator` defines `alignedAllocate`. 94 */ 95 static if (hasMember!(Allocator, "alignedAllocate")) 96 void[] alignedAllocate(size_t bytes, uint alignment) 97 { 98 if (!bytes) return null; 99 if (auto a = allocatorFor(bytes)) 100 { 101 const actual = goodAllocSize(bytes); 102 auto result = a.alignedAllocate(actual, alignment); 103 return result !is null ? (() @trusted => (&result[0])[0 .. bytes])() : null; 104 } 105 return null; 106 } 107 108 /** 109 This method allows expansion within the respective bucket range. It succeeds 110 if both `b.length` and $(D b.length + delta) fall in a range of the form 111 $(D [min + k * step, min + (k + 1) * step - 1]). 112 */ 113 bool expand(ref void[] b, size_t delta) 114 { 115 if (!b || delta == 0) return delta == 0; 116 assert(b.length >= min && b.length <= max); 117 const available = goodAllocSize(b.length); 118 const desired = b.length + delta; 119 if (available < desired) return false; 120 b = (() @trusted => b.ptr[0 .. desired])(); 121 return true; 122 } 123 124 /** 125 This method allows reallocation within the respective bucket range. If both 126 `b.length` and `size` fall in a range of the form $(D [min + k * 127 step, min + (k + 1) * step - 1]), then reallocation is in place. Otherwise, 128 reallocation with moving is attempted. 129 */ 130 bool reallocate(ref void[] b, size_t size) 131 { 132 if (size == 0) 133 { 134 deallocate(b); 135 b = null; 136 return true; 137 } 138 if (size >= b.length && expand(b, size - b.length)) 139 { 140 return true; 141 } 142 assert(b.length >= min && b.length <= max); 143 if (goodAllocSize(size) == goodAllocSize(b.length)) 144 { 145 b = b.ptr[0 .. size]; 146 return true; 147 } 148 // Move cross buckets 149 return common.reallocate(this, b, size); 150 } 151 152 /** 153 Similar to `reallocate`, with alignment. Defined only if `Allocator` 154 defines `alignedReallocate`. 155 */ 156 static if (hasMember!(Allocator, "alignedReallocate")) 157 bool alignedReallocate(ref void[] b, size_t size, uint a) 158 { 159 if (size == 0) 160 { 161 deallocate(b); 162 b = null; 163 return true; 164 } 165 if (size >= b.length && b.ptr.alignedAt(a) && expand(b, size - b.length)) 166 { 167 return true; 168 } 169 assert(b.length >= min && b.length <= max); 170 if (goodAllocSize(size) == goodAllocSize(b.length) && b.ptr.alignedAt(a)) 171 { 172 b = b.ptr[0 .. size]; 173 return true; 174 } 175 // Move cross buckets 176 return common.alignedReallocate(this, b, size, a); 177 } 178 179 /** 180 Defined only if `Allocator` defines `owns`. Finds the owner of `b` and forwards the call to it. 181 */ 182 static if (hasMember!(Allocator, "owns")) 183 Ternary owns(void[] b) 184 { 185 if (!b.ptr) return Ternary.no; 186 if (auto a = allocatorFor(b.length)) 187 { 188 const actual = goodAllocSize(b.length); 189 return a.owns(b.ptr[0 .. actual]); 190 } 191 return Ternary.no; 192 } 193 194 /** 195 This method is only defined if `Allocator` defines `deallocate`. 196 */ 197 static if (hasMember!(Allocator, "deallocate")) 198 bool deallocate(void[] b) 199 { 200 if (!b.ptr) return true; 201 if (auto a = allocatorFor(b.length)) 202 { 203 a.deallocate(b.ptr[0 .. goodAllocSize(b.length)]); 204 } 205 return true; 206 } 207 208 /** 209 This method is only defined if all allocators involved define $(D 210 deallocateAll), and calls it for each bucket in turn. Returns `true` if all 211 allocators could deallocate all. 212 */ 213 static if (hasMember!(Allocator, "deallocateAll")) 214 bool deallocateAll() 215 { 216 bool result = true; 217 foreach (ref a; buckets) 218 { 219 if (!a.deallocateAll()) result = false; 220 } 221 return result; 222 } 223 224 /** 225 This method is only defined if all allocators involved define $(D 226 resolveInternalPointer), and tries it for each bucket in turn. 227 */ 228 static if (hasMember!(Allocator, "resolveInternalPointer")) 229 Ternary resolveInternalPointer(const void* p, ref void[] result) 230 { 231 foreach (ref a; buckets) 232 { 233 Ternary r = a.resolveInternalPointer(p, result); 234 if (r == Ternary.yes) return r; 235 } 236 return Ternary.no; 237 } 238 } 239 240 /// 241 @system unittest 242 { 243 import std.algorithm.comparison : max; 244 import std.experimental.allocator.building_blocks.allocator_list : AllocatorList; 245 import std.experimental.allocator.building_blocks.free_list : FreeList; 246 import std.experimental.allocator.building_blocks.region : Region; 247 import std.experimental.allocator.common : unbounded; 248 import std.experimental.allocator.mallocator : Mallocator; 249 import std.typecons : Ternary; 250 Bucketizer!( 251 FreeList!( 252 AllocatorList!( 253 (size_t n) => Region!Mallocator(max(n, 1024 * 1024))), 254 0, unbounded), 255 65, 512, 64) a; 256 auto b = a.allocate(400); 257 assert(b.length == 400); 258 assert(a.owns(b) == Ternary.yes); 259 a.deallocate(b); 260 } 261 262 @system unittest 263 { 264 import std.algorithm.comparison : max; 265 import std.experimental.allocator.building_blocks.allocator_list : AllocatorList; 266 import std.experimental.allocator.building_blocks.free_list : FreeList; 267 import std.experimental.allocator.building_blocks.region : Region; 268 import std.experimental.allocator.common : unbounded; 269 import std.experimental.allocator.mallocator : Mallocator; 270 import std.typecons : Ternary; 271 272 Bucketizer!( 273 FreeList!( 274 AllocatorList!( 275 (size_t n) => Region!Mallocator(max(n, 1024 * 1024)), Mallocator), 276 0, unbounded), 277 65, 512, 64) a; 278 279 assert((() pure nothrow @safe @nogc => a.goodAllocSize(65))() == 128); 280 281 auto b = a.allocate(100); 282 assert(b.length == 100); 283 // Make reallocate use extend 284 assert((() nothrow @nogc => a.reallocate(b, 101))()); 285 assert(b.length == 101); 286 // Move cross buckets 287 assert((() nothrow @nogc => a.reallocate(b, 200))()); 288 assert(b.length == 200); 289 // Free through realloc 290 assert((() nothrow @nogc => a.reallocate(b, 0))()); 291 assert(b is null); 292 // Ensure deallocate inherits from parent allocators 293 assert((() nothrow @nogc => a.deallocate(b))()); 294 assert((() nothrow @nogc => a.deallocateAll())()); 295 } 296 297 // Test alignedAllocate 298 @system unittest 299 { 300 import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock; 301 import std.experimental.allocator.gc_allocator : GCAllocator; 302 303 Bucketizer!(BitmappedBlock!(64, 8, GCAllocator), 65, 512, 64) a; 304 foreach (ref bucket; a.buckets) 305 { 306 bucket = BitmappedBlock!(64, 8, GCAllocator)(new ubyte[1024]); 307 } 308 309 auto b = a.alignedAllocate(100, 16); 310 assert(b.length == 100); 311 assert(a.alignedAllocate(42, 16) is null); 312 assert(a.alignedAllocate(0, 16) is null); 313 assert((() pure nothrow @safe @nogc => a.expand(b, 0))()); 314 assert(b.length == 100); 315 assert((() pure nothrow @safe @nogc => a.expand(b, 28))()); 316 assert(b.length == 128); 317 assert((() pure nothrow @safe @nogc => !a.expand(b, 1))()); 318 } 319 320 @system unittest 321 { 322 import std.experimental.allocator.building_blocks.bitmapped_block : BitmappedBlock; 323 import std.experimental.allocator.gc_allocator : GCAllocator; 324 325 Bucketizer!(BitmappedBlock!(64, 8, GCAllocator), 1, 512, 64) a; 326 foreach (ref bucket; a.buckets) 327 { 328 bucket = BitmappedBlock!(64, 8, GCAllocator)(new ubyte[1024]); 329 } 330 331 auto b = a.alignedAllocate(1, 4); 332 assert(b.length == 1); 333 // Make reallocate use extend 334 assert(a.alignedReallocate(b, 11, 4)); 335 assert(b.length == 11); 336 // Make reallocate use use realloc because of alignment change 337 assert(a.alignedReallocate(b, 21, 16)); 338 assert(b.length == 21); 339 // Make reallocate use extend 340 assert(a.alignedReallocate(b, 22, 16)); 341 assert(b.length == 22); 342 // Move cross buckets 343 assert(a.alignedReallocate(b, 101, 16)); 344 assert(b.length == 101); 345 // Free through realloc 346 assert(a.alignedReallocate(b, 0, 16)); 347 assert(b is null); 348 }