1 module mempooled.fixed; 2 3 debug import core.stdc.stdio; 4 import core.stdc.stdlib; 5 import std.conv : emplace; 6 7 nothrow @nogc: 8 9 /** 10 * Create instance of `FixedPool` 11 * 12 * Params: 13 * T = type of the pooled item 14 * blockSize = size of one block in a pool 15 * size = number of items 16 * buffer = optional buffer to initiate `FixedPool` over 17 */ 18 auto fixedPool(T, size_t size)(ubyte[] buffer = null) 19 { 20 auto res = FixedPool!(T.sizeof, size, T)(); 21 res.initPool(buffer); 22 return res; 23 } 24 25 /// ditto 26 auto fixedPool(size_t blockSize, size_t numBlocks)(ubyte[] buffer = null) 27 { 28 auto res = FixedPool!(blockSize, numBlocks, void)(); 29 res.initPool(buffer); 30 return res; 31 } 32 33 /** 34 * Implementation of "Fast Efficient Fixed-Size Memory Pool" as described in this article: 35 * www.thinkmind.org/download.php?articleid=computation_tools_2012_1_10_80006 36 * 37 * Implementation of "Fast Efficient Fixed-Size Memory Pool" as described in 38 * [this](www.thinkmind.org/download.php?articleid=computation_tools_2012_1_10_80006) article. 39 * 40 * It can work as a pool for single templated type or generic pool with a fixed block size (so one 41 * can `alloc` various types with size less or equal to the block size). 42 * 43 * Minimal block size is 4B as data in blocks are used internally to form a linked list of the blocks. 44 * 45 * Internally it uses refcounted payload so can be copied around as needed. 46 * 47 * Params: 48 * blockSize = size of one item block in a pool 49 * numBlock = number of blocks in a pool 50 * T = optional forced type of pooled items - if used, pool is forced to provide items of this type only 51 * 52 * See_Also: implementation here: https://github.com/green-anger/MemoryPool 53 */ 54 struct FixedPool(size_t blockSize, size_t numBlocks, T = void) 55 { 56 nothrow @nogc: 57 58 static assert(blockSize >= 4, "blockSize must be equal or greater than uint.sizeof"); 59 static if (!is(T == void)) 60 { 61 static assert(T.sizeof == blockSize, "Blocksize must be the same as used T.sizeof"); 62 } 63 64 private 65 { 66 struct Payload 67 { 68 nothrow @nogc: 69 70 ubyte* memStart; // Beginning of memory pool 71 ubyte* next; // Num of next free block 72 uint numFreeBlocks; // Num of remaining blocks 73 uint numInitialized; // Num of initialized blocks 74 size_t refs; // Number of references 75 bool ownPool; // Is memory block allocated by us? 76 77 void initialize(ubyte[] buffer) 78 { 79 assert(buffer is null || buffer.length == numBlocks * blockSize, "Provided buffer has wrong size, must be numBlocks*blockSize"); 80 if (buffer) memStart = &buffer[0]; 81 else 82 { 83 memStart = cast(ubyte*)calloc(numBlocks, blockSize); 84 assert(memStart, "failed to allocate pool"); 85 ownPool = true; 86 } 87 88 next = memStart; 89 numFreeBlocks = numBlocks; 90 refs = 1; 91 } 92 93 ~this() 94 { 95 assert(memStart, "memStart is null"); 96 if (ownPool) free(memStart); 97 } 98 } 99 100 Payload* pay; 101 } 102 103 /// Copy constructor 104 this(ref return scope typeof(this) rhs) 105 { 106 // debug printf("copy\n"); 107 if (rhs.pay is null) initPool(); 108 else 109 { 110 this.pay = rhs.pay; 111 this.pay.refs++; 112 } 113 } 114 115 /// Destructor 116 ~this() 117 { 118 if (pay) 119 { 120 pay.refs--; 121 // debug printf("destroy: refs=%d\n", pay.refs); 122 if (pay.refs == 0) 123 { 124 // debug printf("free\n"); 125 destroy(*pay); // call payload destructor 126 free(pay); 127 } 128 } 129 } 130 131 /// Available number of items / blocks that can be allocated 132 size_t capacity() const 133 { 134 if (pay is null) return numBlocks; 135 return pay.numFreeBlocks; 136 } 137 138 static if (is(T == void)) // allow to allocate anything that fits 139 { 140 /** 141 * Allocates item of requested type from the pool. 142 * 143 * Size of the requested item type must be less or equal to the pool block size. 144 * 145 * Params: args = optional args to be used to initialize item 146 * Returns: pointer to the allocated item or `null` if the pool is already depleted. 147 */ 148 U* alloc(U, ARGS...)(ARGS args) 149 { 150 pragma(inline) 151 static assert(U.sizeof <= blockSize, format!"Can't allocate %s of size %s with blockSize=%s"(U, U.sizeof, blockSize)); 152 void* p = allocImpl(); 153 if (p) return emplace(cast(U*)p, args); 154 return null; 155 } 156 157 /** 158 * Returns previously allocated item back to the pool. 159 * 160 * If the item type has a destructor it is called. 161 * 162 * Params: p = allocated item pointer 163 */ 164 void dealloc(U)(U* p) 165 { 166 pragma(inline, true) 167 deallocImpl(p); 168 } 169 } 170 else 171 { 172 /** 173 * Allocates item from the pool. 174 * 175 * Params: args = optional args to be used to initialize item 176 * Returns: pointer to the allocated item or `null` if the pool is already depleted. 177 */ 178 T* alloc(ARGS...)(ARGS args) 179 { 180 pragma(inline) 181 void* p = allocImpl(); 182 if (p) return emplace(cast(T*)p, args); 183 return null; 184 } 185 186 /** 187 * Returns previously allocated item back to the pool. 188 * 189 * If the item type has a destructor it is called. 190 * 191 * Params: p = allocated item pointer 192 */ 193 void dealloc(T* p) 194 { 195 pragma(inline, true) 196 deallocImpl(p); 197 } 198 } 199 200 private: 201 202 void initPool(ubyte[] buffer = null) 203 { 204 assert(pay is null); 205 // debug printf("init\n"); 206 pay = cast(Payload*)malloc(Payload.sizeof); 207 emplace(pay); 208 pay.initialize(buffer); 209 } 210 211 void* allocImpl() 212 { 213 if (pay is null) initPool(); 214 215 // make sure that list of unused blocks is correct when allocating 216 if (pay.numInitialized < numBlocks) 217 { 218 uint* p = cast(uint*)addrFromIdx(pay.memStart, pay.numInitialized); 219 *p = ++pay.numInitialized; 220 } 221 222 void* ret; 223 if (pay.numFreeBlocks > 0) 224 { 225 ret = cast(void*)pay.next; 226 if (--pay.numFreeBlocks != 0) 227 pay.next = addrFromIdx(pay.memStart, *(cast(uint*)pay.next)); 228 else pay.next = null; 229 } 230 return ret; 231 } 232 233 void deallocImpl(U)(U* p) 234 { 235 assert( 236 cast(ubyte*)p >= pay.memStart && cast(ubyte*)p < (pay.memStart + numBlocks*blockSize), 237 "Out of bounds" 238 ); 239 assert((cast(ubyte*)p - pay.memStart) % blockSize == 0, "Invalid item memory offset"); 240 241 import std.traits : hasElaborateDestructor; 242 static if (hasElaborateDestructor!U) 243 destroy(*p); // call possible destructors 244 245 // store index of prev next to newly returned item 246 if (pay.next !is null) 247 *(cast(uint*)p) = idxFromAddr(pay.memStart, pay.next); 248 else 249 *(cast(uint*)p) = numBlocks; 250 251 // and use returned item as next free one 252 pay.next = cast(ubyte*)p; 253 ++pay.numFreeBlocks; 254 } 255 256 static ubyte* addrFromIdx(const ubyte* mstart, uint i) pure 257 { 258 pragma(inline, true) 259 return cast(ubyte*)(mstart + ( i * blockSize)); 260 } 261 262 static uint idxFromAddr(const ubyte* mstart, const ubyte* p) pure 263 { 264 pragma(inline, true) 265 return ((cast(uint)(p - mstart)) / blockSize); 266 } 267 } 268 269 @("internal implementation test") 270 unittest 271 { 272 auto pool = fixedPool!(int, 10); 273 foreach (i; 0..10) 274 { 275 auto item = pool.alloc; 276 assert(*item == 0); 277 *item = i; 278 } 279 assert(pool.capacity == 0); 280 assert(pool.pay.next is null); 281 282 pool.dealloc(cast(int*)pool.pay.memStart); // dealocate first 283 assert(pool.capacity == 1); 284 assert(pool.pay.next == pool.pay.memStart); 285 286 auto i = pool.alloc(); 287 assert(pool.capacity == 0); 288 assert(pool.pay.next is null); 289 290 pool.dealloc(cast(int*)pool.pay.memStart); // dealocate it back 291 pool.dealloc(cast(int*)(pool.pay.memStart + int.sizeof*9)); // deallocate last one 292 auto p = pool.alloc; 293 assert(pool.pay.next == pool.pay.memStart); 294 assert(cast(ubyte*)p == pool.pay.memStart + int.sizeof*9); 295 } 296 297 @("payload destructor") 298 unittest 299 { 300 static int refs; 301 struct Foo 302 { 303 ~this() nothrow @nogc { refs--; } 304 } 305 306 auto pool = fixedPool!(Foo, 10); 307 alias PFoo = Foo*; 308 309 PFoo[10] foos; 310 foreach (i; 0..10) foos[i] = pool.alloc(); 311 refs = 10; 312 foreach (i; 0..10) pool.dealloc(foos[i]); 313 assert(refs == 0); 314 } 315 316 @("capacity") 317 unittest 318 { 319 auto pool = fixedPool!(int, 10); 320 assert(pool.capacity == 10); 321 foreach (_; 0..10) 322 { 323 auto p = pool.alloc(); 324 assert(p !is null); 325 } 326 assert(pool.capacity == 0); 327 assert(pool.alloc() is null); // no more space 328 } 329 330 @("untyped") 331 unittest 332 { 333 static int refs; 334 struct Foo 335 { 336 ~this() nothrow @nogc { refs--; } 337 } 338 339 struct Bar 340 { 341 int baz; 342 this(int i) nothrow @nogc { baz = i; } 343 ~this() nothrow @nogc { refs--; } 344 } 345 346 auto pool = fixedPool!(100, 1); 347 assert(pool.capacity == 1); 348 auto f = pool.alloc!(Foo); 349 refs++; 350 pool.dealloc(f); 351 f = null; 352 assert(refs == 0); 353 354 auto b = pool.alloc!Bar(42); 355 refs++; 356 assert(b.baz == 42); 357 pool.dealloc(b); 358 b = null; 359 assert(refs == 0); 360 361 auto x = pool.alloc!int(); 362 assert(x !is null); 363 auto y = pool.alloc!int(); 364 assert(y is null); 365 } 366 367 @("copy") 368 unittest 369 { 370 auto pool = fixedPool!(int, 10); 371 assert(pool.pay.refs == 1); 372 auto pool2 = pool; 373 assert(pool.pay.refs == 2); 374 assert(pool.pay is pool2.pay); 375 } 376 377 @("custom buffer") 378 unittest 379 { 380 auto buf = (cast(ubyte*)malloc(int.sizeof * 1024))[0..1024*int.sizeof]; 381 auto pool = fixedPool!(int, 1024)(buf); 382 auto i = pool.alloc(); 383 assert(cast(ubyte*)i == &buf[0]); 384 }