1//
2// MemoryPool.h
3//
4// Library: Foundation
5// Package: Core
6// Module: MemoryPool
7//
8// Definition of the MemoryPool and FastMemoryPool classes.
9//
10// Copyright (c) 2005-2006, Applied Informatics Software Engineering GmbH.
11// and Contributors.
12//
13// SPDX-License-Identifier: BSL-1.0
14//
15
16
17#ifndef Foundation_MemoryPool_INCLUDED
18#define Foundation_MemoryPool_INCLUDED
19
20
21#include "Poco/Foundation.h"
22#include "Poco/Alignment.h"
23#include "Poco/Mutex.h"
24#include "Poco/NestedDiagnosticContext.h"
25#include <vector>
26#include <cstring>
27#include <cstddef>
28#include <iostream>
29
30
31namespace Poco {
32
33
34class Foundation_API MemoryPool
35 /// A simple pool for fixed-size memory blocks.
36 ///
37 /// The main purpose of this class is to speed-up
38 /// memory allocations, as well as to reduce memory
39 /// fragmentation in situations where the same blocks
40 /// are allocated all over again, such as in server
41 /// applications.
42 ///
43 /// All allocated blocks are retained for future use.
44 /// A limit on the number of blocks can be specified.
45 /// Blocks can be preallocated.
46{
47public:
48 MemoryPool(std::size_t blockSize, int preAlloc = 0, int maxAlloc = 0);
49 /// Creates a MemoryPool for blocks with the given blockSize.
50 /// The number of blocks given in preAlloc are preallocated.
51
52 ~MemoryPool();
53
54 void* get();
55 /// Returns a memory block. If there are no more blocks
56 /// in the pool, a new block will be allocated.
57 ///
58 /// If maxAlloc blocks are already allocated, an
59 /// OutOfMemoryException is thrown.
60
61 void release(void* ptr);
62 /// Releases a memory block and returns it to the pool.
63
64 std::size_t blockSize() const;
65 /// Returns the block size.
66
67 int allocated() const;
68 /// Returns the number of allocated blocks.
69
70 int available() const;
71 /// Returns the number of available blocks in the pool.
72
73private:
74 MemoryPool();
75 MemoryPool(const MemoryPool&);
76 MemoryPool& operator = (const MemoryPool&);
77
78 void clear();
79
80 enum
81 {
82 BLOCK_RESERVE = 128
83 };
84
85 typedef std::vector<char*> BlockVec;
86
87 std::size_t _blockSize;
88 int _maxAlloc;
89 int _allocated;
90 BlockVec _blocks;
91 FastMutex _mutex;
92};
93
94
95//
96// inlines
97//
98inline std::size_t MemoryPool::blockSize() const
99{
100 return _blockSize;
101}
102
103
104inline int MemoryPool::allocated() const
105{
106 return _allocated;
107}
108
109
110inline int MemoryPool::available() const
111{
112 return (int) _blocks.size();
113}
114
115
116//
117// FastMemoryPool
118//
119
120// Macro defining the default initial size of any
121// FastMemoryPool; can be overriden by specifying
122// FastMemoryPool pre-alloc at runtime.
123#define POCO_FAST_MEMORY_POOL_PREALLOC 1000
124
125
126template <typename T, typename M = SpinlockMutex>
127class FastMemoryPool
128 /// FastMemoryPool is a class for pooling fixed-size blocks of memory.
129 ///
130 /// The main purpose of this class is to speed-up memory allocations,
131 /// as well as to reduce memory fragmentation in situations where the
132 /// same blocks are allocated all over again, such as in server
133 /// applications. It differs from the MemoryPool in the way the block
134 /// size is determined - it is inferred form the held type size and
135 /// applied statically. It is also, as its name implies, faster than
136 /// Poco::MemoryPool. It is likely to be significantly faster than
137 /// the runtime platform generic memory allocation functionality
138 /// as well, but it has certain limitations (aside from only giving
139 /// blocks of fixed size) - see more below.
140 ///
141 /// An object using memory from the pool should be created using
142 /// in-place new operator; once released back to the pool, its
143 /// destructor will be called by the pool. The returned pointer
144 /// must be a valid pointer to the type for which it was obtained.
145 ///
146 /// Example use:
147 ///
148 /// using std::vector;
149 /// using std:string;
150 /// using std::to_string;
151 /// using Poco::FastMemoryPool;
152 ///
153 /// int blocks = 10;
154 /// FastMemoryPool<int> fastIntPool(blocks);
155 /// FastMemoryPool<string> fastStringPool(blocks);
156 ///
157 /// vector<int*> intVec(blocks, 0);
158 /// vector<string*> strVec(blocks);
159 ///
160 /// for (int i = 0; i < blocks; ++i)
161 /// {
162 /// intVec[i] = new (fastIntPool.get()) int(i);
163 /// strVec[i] = new (fastStringPool.get()) string(to_string(i));
164 /// }
165 ///
166 /// for (int i = 0; i < blocks; ++i)
167 /// {
168 /// fastIntPool.release(intVec[i]);
169 /// fastStringPool.release(strVec[i]);
170 /// }
171 ///
172 /// Pool keeps memory blocks in "buckets". A bucket is an array of
173 /// blocks; it is always allocated with a single `new[]`, and its blocks
174 /// are initialized at creation time. Whenever the current capacity
175 /// of the pool is reached, a new bucket is allocated and its blocks
176 /// initialized for internal use. If the new bucket allocation would
177 /// exceed allowed maximum size, std::bad_alloc() exception is thrown,
178 /// with object itself left intact.
179 ///
180 /// Pool internally keeps track of available blocks through a linked-list
181 /// and utilizes unused memory blocks for that purpose. This means that,
182 /// for types smaller than pointer the size of a block will be greater
183 /// than the size of the type. The implications are following:
184 ///
185 /// - FastMemoryPool can not be used for arrays of types smaller
186 /// than pointer
187 ///
188 /// - if FastMemoryPool is used to store variable-size arrays, it
189 /// must not have multiple buckets; the way to achieve this is by
190 /// specifying proper argument values at construction.
191 ///
192 /// Neither of the above are primarily intended or recommended modes
193 /// of use. It is recommended to use a FastMemoryPool for creation of
194 /// many objects of the same type. Furthermore, it is perfectly fine
195 /// to have arrays or STL containers of pointers to objects created
196 /// in blocks of memory obtained from the FastMemoryPool.
197 ///
198 /// Before a block is given to the user, it is removed from the list;
199 /// when a block is returned to the pool, it is re-inserted in the
200 /// list. Pool will return held memory to the system at destruction,
201 /// and will not leak memory after destruction; this means that after
202 /// pool destruction, any memory that was taken from, but not returned
203 /// to the pool becomes invalid.
204 ///
205 /// FastMemoryPool is thread safe; it uses Poco::SpinlockMutex by
206 /// default, but other mutexes can be specified through te template
207 /// parameter, if needed. Poco::NullMutex can be specified as template
208 /// parameter to avoid locking and improve speed in single-threaded
209 /// scenarios.
210{
211private:
212 class Block
213 /// A block of memory. This class represents a memory
214 /// block. It has dual use, the primary one being
215 /// obvious - memory provided to the user of the pool.
216 /// The secondary use is for internal "housekeeping"
217 /// purposes.
218 ///
219 /// It works like this:
220 ///
221 /// - when initially created, a Block is properly
222 /// constructed and positioned into the internal
223 /// linked list of blocks
224 ///
225 /// - when given to the user, the Block is removed
226 /// from the internal linked list of blocks
227 ///
228 /// - when returned back to the pool, the Block
229 /// is again in-place constructed and inserted
230 /// as next available block in the linked list
231 /// of blocks
232 {
233 public:
234
235 Block()
236 /// Creates a Block and sets its next pointer.
237 /// This constructor should ony be used to initialize
238 /// a block sequence (an array of blocks) in a newly
239 /// allocated bucket.
240 ///
241 /// After the construction, the last block's `next`
242 /// pointer points outside the allocated memory and
243 /// must be set to zero. This design improves performance,
244 /// because otherwise the block array would require an
245 /// initialization loop after the allocation.
246 {
247 _memory.next = this + 1;
248 }
249
250 explicit Block(Block* next)
251 /// Creates a Block and sets its next pointer.
252 {
253 _memory.next = next;
254 }
255
256 union
257 /// Memory block storage.
258 ///
259 /// Note that this storage is properly aligned
260 /// for the datatypes it holds. It will not work
261 /// for arrays of types smaller than pointer size.
262 /// Furthermore, the pool itself will not work for
263 /// a variable-size array of any type after it is
264 /// resized.
265 {
266 char buffer[sizeof(T)];
267 Block* next;
268 } _memory;
269
270 private:
271 Block(const Block&);
272 Block& operator = (const Block&);
273 Block(Block&&);
274 Block& operator = (Block&&);
275 };
276
277public:
278 typedef M MutexType;
279 typedef typename M::ScopedLock ScopedLock;
280
281 typedef Block* Bucket;
282 typedef std::vector<Bucket> BucketVec;
283
284 FastMemoryPool(std::size_t blocksPerBucket = POCO_FAST_MEMORY_POOL_PREALLOC, std::size_t bucketPreAlloc = 10, std::size_t maxAlloc = 0):
285 _blocksPerBucket(blocksPerBucket),
286 _maxAlloc(maxAlloc),
287 _available(0)
288 /// Creates the FastMemoryPool.
289 ///
290 /// The size of a block is inferred from the type size. Number of blocks
291 /// per bucket, pre-allocated bucket pointer storage and maximum allowed
292 /// total size of the pool can be customized by overriding default
293 /// parameter value:
294 ///
295 /// - blocksPerBucket specifies how many blocks each bucket contains
296 /// defaults to POCO_FAST_MEMORY_POOL_PREALLOC
297 ///
298 /// - bucketPreAlloc specifies how much space for bucket pointers
299 /// (buckets themselves are not prealocated) will be
300 /// pre-alocated.
301 ///
302 /// - maxAlloc specifies maximum allowed total pool size in bytes.
303 {
304 if (_blocksPerBucket < 2)
305 throw std::invalid_argument("FastMemoryPool: blocksPerBucket must be >=2");
306 _buckets.reserve(bucketPreAlloc);
307 resize();
308 }
309
310 ~FastMemoryPool()
311 /// Destroys the FastMemoryPool and releases all memory.
312 /// Any emory taken from, but not returned to, the pool
313 /// becomes invalid.
314 {
315 clear();
316 }
317
318 void* get()
319 /// Returns pointer to the next available
320 /// memory block. If the pool is exhausted,
321 /// it will be resized by allocating a new
322 /// bucket.
323 {
324 Block* ret;
325 {
326 ScopedLock l(_mutex);
327 if(_firstBlock == 0) resize();
328 ret = _firstBlock;
329 _firstBlock = _firstBlock->_memory.next;
330 }
331 --_available;
332 return ret;
333 }
334
335 template <typename P>
336 void release(P* ptr)
337 /// Recycles the released memory by initializing it for
338 /// internal use and setting it as next available block;
339 /// previously next block becomes this block's next.
340 /// Releasing of null pointers is silently ignored.
341 /// Destructor is called for the returned pointer.
342 {
343 if (!ptr) return;
344 reinterpret_cast<P*>(ptr)->~P();
345 ++_available;
346 ScopedLock l(_mutex);
347 _firstBlock = new (ptr) Block(_firstBlock);
348 }
349
350 std::size_t blockSize() const
351 /// Returns the block size in bytes.
352 {
353 return sizeof(Block);
354 }
355
356 std::size_t allocated() const
357 /// Returns the total amount of memory allocated, in bytes.
358 {
359 return _buckets.size() * _blocksPerBucket;
360 }
361
362 std::size_t available() const
363 /// Returns currently available amount of memory in bytes.
364 {
365 return _available;
366 }
367
368private:
369 FastMemoryPool(const FastMemoryPool&) = delete;
370 FastMemoryPool& operator = (const FastMemoryPool&) = delete;
371 FastMemoryPool(FastMemoryPool&&) = delete;
372 FastMemoryPool& operator = (FastMemoryPool&&) = delete;
373
374 void resize()
375 /// Creates new bucket and initializes it for internal use.
376 /// Sets the previously next block to point to the new bucket's
377 /// first block and the new bucket's last block becomes the
378 /// last block.
379 {
380 if (_buckets.size() == _buckets.capacity())
381 {
382 std::size_t newSize = _buckets.capacity() * 2;
383 if (_maxAlloc != 0 && newSize > _maxAlloc) throw std::bad_alloc();
384 _buckets.reserve(newSize);
385 }
386 _buckets.emplace_back(new Block[_blocksPerBucket]);
387 _firstBlock = _buckets.back();
388 // terminate last block
389 _firstBlock[_blocksPerBucket-1]._memory.next = 0;
390 _available += _blocksPerBucket;
391 }
392
393 void clear()
394 {
395 for (auto& block : _buckets) delete[] block;
396 }
397
398 typedef std::atomic<std::size_t> Counter;
399
400 std::size_t _blocksPerBucket;
401 BucketVec _buckets;
402 Block* _firstBlock;
403 std::size_t _maxAlloc;
404 Counter _available;
405 mutable M _mutex;
406};
407
408
409} // namespace Poco
410
411
412#endif // Foundation_MemoryPool_INCLUDED
413