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 }