1 /** 2 * (Hopefully) useful _allocators. 3 * Copyright: © 2014 Economic Modeling Specialists, Intl. 4 * Authors: Brian Schott 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 8 module memory.allocators; 9 10 import std.allocator; 11 12 /** 13 * Allocator used for allocating nodes of a fixed size. 14 */ 15 template NodeAllocator(size_t nodeSize, size_t blockSize = 1024) 16 { 17 enum ns = roundUpToMultipleOf( 18 nodeSize >= (void*).sizeof ? nodeSize : (void*).sizeof, (void*).sizeof); 19 static assert (ns <= BlockAllocator!(blockSize).maxAllocationSize); 20 alias NodeAllocator = Freelist!(BlockAllocator!(blockSize), ns, ns); 21 } 22 23 /// 24 unittest 25 { 26 enum testSize = 4_000; 27 static struct Node { Node* next; int payload; } 28 NodeAllocator!(Node.sizeof, 2048) nodeAllocator; 29 Node*[testSize] nodes; 30 foreach (i; 0 .. testSize) 31 nodes[i] = allocate!Node(nodeAllocator); 32 foreach (i; 0 .. testSize) 33 assert (nodes[i] !is null); 34 foreach (i; 0 .. testSize) 35 deallocate(nodeAllocator, nodes[i]); 36 } 37 38 /** 39 * Allocator that performs most small allocations on the stack, then falls over 40 * to malloc/free when necessary. 41 */ 42 template QuickAllocator(size_t stackCapacity) 43 { 44 alias QuickAllocator = FallbackAllocator!(InSituRegion!stackCapacity, Mallocator); 45 } 46 47 /// 48 unittest 49 { 50 QuickAllocator!1024 quick; 51 void[] mem = quick.allocate(1_000); 52 assert (mem); 53 quick.deallocate(mem); 54 mem = quick.allocate(10_000); 55 assert (mem); 56 quick.deallocate(mem); 57 } 58 59 /** 60 * Simple allocator that allocates memory in chucks of blockSize, and then frees 61 * all of its blocks when it goes out of scope. The block allocator is reference 62 * counted, so it may be copied safely. It does not support freeing any 63 * memory allocated by it. Deallocation only occurs by destroying the entire 64 * allocator. Note that it is not possible to allocate blockSize bytes with this 65 * allocator due to some memory being used for internal record keeping. 66 */ 67 struct BlockAllocator(size_t blockSize) 68 { 69 /** 70 * Copy construction disabled because this struct clears its memory with a 71 * destructor. 72 */ 73 @disable this(this); 74 75 /** 76 * Frees all memory allocated by this allocator 77 */ 78 ~this() pure nothrow @trusted 79 { 80 Node* current = root; 81 Node* previous = void; 82 while (current !is null) 83 { 84 previous = current; 85 current = current.next; 86 assert (previous == previous.memory.ptr); 87 Mallocator.it.deallocate(previous.memory); 88 } 89 root = null; 90 } 91 92 /** 93 * Standard allocator operation. 94 */ 95 void[] allocate(size_t bytes) pure nothrow @trusted 96 in 97 { 98 import std.string; 99 assert (bytes <= maxAllocationSize, format("Cannot allocate %d bytes" 100 ~ " from an allocator with block size of %d and max allocation" 101 ~ " size of %d", bytes, blockSize, maxAllocationSize)); 102 } 103 out (result) 104 { 105 import std.string; 106 assert (result.length == bytes, format("Allocated %d bytes when %d" 107 ~ " bytes were requested.", result.length, bytes)); 108 } 109 body 110 { 111 // Allocate from the beginning of the list. Filled blocks go later in 112 // the list. 113 // Give up after three blocks. We don't want to do a full linear scan. 114 size_t i = 0; 115 for (Node* current = root; current !is null && i < 3; current = current.next) 116 { 117 void[] mem = allocateInNode(current, bytes); 118 if (mem !is null) 119 return mem; 120 i++; 121 } 122 Node* n = allocateNewNode(); 123 void[] mem = allocateInNode(n, bytes); 124 n.next = root; 125 root = n; 126 return mem; 127 } 128 129 /** 130 * The maximum number of bytes that can be allocated at a time with this 131 * allocator. This is smaller than blockSize because of some internal 132 * bookkeeping information. 133 */ 134 enum maxAllocationSize = blockSize - Node.sizeof; 135 136 /** 137 * Allocator's memory alignment 138 */ 139 enum alignment = platformAlignment; 140 141 private: 142 143 /** 144 * Allocates a new node along with its memory 145 */ 146 Node* allocateNewNode() pure nothrow const @trusted 147 { 148 void[] memory = Mallocator.it.allocate(blockSize); 149 Node* n = cast(Node*) memory.ptr; 150 n.used = roundUpToMultipleOf(Node.sizeof, platformAlignment); 151 n.memory = memory; 152 n.next = null; 153 return n; 154 } 155 156 /** 157 * Allocates memory from the given node 158 */ 159 void[] allocateInNode(Node* node, size_t bytes) pure nothrow const @safe 160 in 161 { 162 assert (node !is null); 163 } 164 body 165 { 166 if (node.used + bytes > node.memory.length) 167 return null; 168 immutable prev = node.used; 169 node.used = roundUpToMultipleOf(node.used + bytes, platformAlignment); 170 return node.memory[prev .. prev + bytes]; 171 } 172 173 /** 174 * Single linked list of allocated blocks 175 */ 176 static struct Node 177 { 178 void[] memory; 179 size_t used; 180 Node* next; 181 } 182 183 /** 184 * Pointer to the first item in the node list 185 */ 186 Node* root; 187 188 /** 189 * Returns s rounded up to a multiple of base. 190 */ 191 static size_t roundUpToMultipleOf(size_t s, uint base) pure nothrow @safe 192 { 193 assert(base); 194 auto rem = s % base; 195 return rem ? s + base - rem : s; 196 } 197 } 198 199 unittest 200 { 201 BlockAllocator!(1024 * 4 * 10) blockAllocator; 202 void[] mem = blockAllocator.allocate(10); 203 assert (mem); 204 void[] mem2 = blockAllocator.allocate(10_000); 205 assert (mem2); 206 } 207 208 private size_t roundUpToMultipleOf(size_t s, uint base) pure nothrow @safe 209 { 210 assert(base); 211 auto rem = s % base; 212 return rem ? s + base - rem : s; 213 }