Planeshift
poolallocator.h
Go to the documentation of this file.
1 /*
2  * poolallocator.h
3  *
4  * Copyright (C) 2001 Atomic Blue (info@planeshift.it, http://www.atomicblue.org)
5  *
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation (version 2 of the License)
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  *
18  *
19  * A template class used to allocate fixed size objects in pools.
20  *
21  */
22 
23 #ifndef PS_POOLALLOCATOR_H
24 #define PS_POOLALLOCATOR_H
25 
26 // Define this to enable testing to be sure a Release()'d object came from the pool to begin with (debug mode only)
27 //#define POOLALLOC_DEBUG_DELETE
28 
29 #include <csutil/threading/thread.h>
30 
35 /* Design notes:
36  *
37  * This template is designed to be integrated into a class definition.
38  * Override the new and delete operators for the class to call CallFromNew() and
39  * CallFromDelete() respectively.
40  * Add a private static member variable to the class declaration like so:
41  * static PoolAllocator<myclass> mypool;
42  *
43  * Add the definition (usually in the .cpp file):
44  * PoolAllocator<myclass> myclass::mypool(number of objects to allocate per "step");
45  *
46  * Use new and delete as normal throughout the program.
47  *
48  * DO NOT DERIVE FROM A CLASS USING A POOL ALLOCATOR UNLESS YOU DO THE FOLLOWING:
49  * 1) Create a new static PoolAllocator<> member for the derived class.
50  * 2) Implement the new and delete operators as above using the new PoolAllocator<> member.
51  *
52  * Benefits:
53  * Pool allocation using this method is very fast. It usually involves a test, an assignment
54  * and a return. No mutex locking is needed since this is not an attempt at being thread safe.
55  *
56  * Limitations:
57  * 1) Each pool operates on fixed-size objects. These have to be at least the size of struct
58  * st_freelist_member (currently a single pointer. Which is 4 bytes on 32 bit platforms.)
59  * or the size of each object is bumped up to this size (wasting memory).
60  * 2) Memory is never returned to the OS until the entire pool is destructed. In the suggested
61  * implementation this is at program exit.
62  * 3) Allocation time is not constant (it isn't on the heap either). When the pool empties more
63  * space is allocated from the heap and the pool is filled. The larger the allocation step
64  * size (specified as a parameter to the constructor) the longer this process takes but the
65  * process will have to be done less often.
66  * 4) THIS IS NOT THREADSAFE!! To make this threadsafe the ideal solution would be atomic
67  * operations (there is a nice paper on using atomic ops to safely handle linked lists
68  * out there.) An alternative is to mutex most calls - which further reduces the benefit
69  * over heap allocation.
70  *
71  * - A pool size of 3 should be about as efficient as normal heap allocation functions
72  *
73  */
74 
75 #define POOLALLOC_DEFAULT_ALLOCATION_OBJECTS 1024
76 
77 
78 
79 template <class TEMPLATE_CLASS>
81 {
82  CS::Threading::Mutex mutex;
83 
85  struct st_pool_entry
86  {
87  struct st_pool_entry *next;
88  };
89 
95  struct st_freelist_member
96  {
97  struct st_freelist_member *next;
98  };
99 
100 
102  struct st_freelist_member *freelist_head;
104  struct st_pool_entry *poollist_head;
110  uint32 allocation_step_itemcount;
111 
112 public:
115  {
116  allocation_step_itemcount = items_per_pool;
117  freelist_head = NULL;
118  poollist_head = NULL;
119  }
120 
123  {
124  struct st_pool_entry *pool=poollist_head,*nextpool;
125 
126  // Step through each pool, freeing the memory associated
127  while (pool)
128  {
129  nextpool=pool->next;
130  free(pool);
131  pool=nextpool;
132  }
133  poollist_head=NULL;
134  freelist_head=NULL;
135  }
136 
137 
139  TEMPLATE_CLASS *CallFromNew()
140  {
141  CS_ASSERT_MSG("Contention detected in PoolAllocator new", mutex.TryLock());
142  TEMPLATE_CLASS *objptr;
143 
144  // Allocate another pool if no free blocks are available
145  if (!freelist_head)
146  AllocNewPool();
147 
148  // Test for new pool allocation failure
149  if (!freelist_head)
150  {
151  mutex.Unlock();
152  return (TEMPLATE_CLASS *)NULL;
153  }
154 
155  // Bump a block off the free list and return it
156  objptr=(TEMPLATE_CLASS *)freelist_head;
157  freelist_head=freelist_head->next;
158 
159  mutex.Unlock();
160  return objptr;
161  }
162 
164  void CallFromDelete(TEMPLATE_CLASS *obj)
165  {
166  CS_ASSERT_MSG("Contention detected in PoolAllocator delete", mutex.TryLock());
167 
168  // NULL pointers are OK - just like delete.
169  if (!obj)
170  {
171  mutex.Unlock();
172  return;
173  }
174 #ifdef POOLALLOC_DEBUG_DELETE
175  // Throw an assert if this block didn't come from this PoolAllocator
176  CS_ASSERT(ObjectCameFromPool(obj));
177 #endif
178  // Clear memory to be on the safe side
179  memset(obj, 0xDD, sizeof(TEMPLATE_CLASS));
180 
181  // Add this entry to the head of the free list
182  ((struct st_freelist_member *)obj)->next=freelist_head;
183  freelist_head=((struct st_freelist_member *)obj);
184  mutex.Unlock();
185  }
186 
187  bool PointerCameFromPool(void *ptr)
188  {
189  return (ObjectCameFromPool((TEMPLATE_CLASS *)ptr)!=NULL);
190  }
191 
192 
193 private:
195  struct st_pool_entry *ObjectCameFromPool(TEMPLATE_CLASS *obj)
196  {
197  int entrysize;
198  struct st_pool_entry *testentry=poollist_head;
199 
200  // Determine the size of blocks
201  entrysize=sizeof(TEMPLATE_CLASS);
202  if (entrysize<sizeof(struct st_freelist_member))
203  entrysize=sizeof(struct st_freelist_member);
204 
205  // Walk through each pool
206  while (testentry)
207  {
208  /* Each pool consists of:
209  * 1 struct st_pool_entry (4 bytes)
210  * (allocation_step_itemcount) blocks of (entrysize) bytes
211  *
212  * We know that if an object came from the pool it must satisfy the following tests:
213  * 1) Its pointer falls after the pool pointer and prior to the pool pointer + the pool size
214  * 2) Its offset from the pool pointer + header must be divisible by the block size
215  */
216  if ((uint8 *)obj>(uint8 *)testentry &&
217  (uint8 *)obj<(uint8 *)testentry + sizeof(st_pool_entry)+entrysize*allocation_step_itemcount)
218  {
219  if ((unsigned long)((uint8 *)obj-((uint8 *)testentry + sizeof(st_pool_entry))) % (unsigned long)entrysize)
220  {
221  // This pointer is within a pool, but does not fall on an object boundary!
222  return NULL;
223  }
224  return testentry;
225  }
226  testentry=testentry->next;
227  }
228  return NULL;
229  }
230 
231 
233  void AllocNewPool()
234  {
235  // Different allocation methods depending on the size of the class
236  if (sizeof(TEMPLATE_CLASS)<sizeof(struct st_freelist_member))
237  {
238  // Using the sizeof struct st_freelist_member as the size of blocks
239  uint8 *buffer;
240  struct st_freelist_member *workmember;
241  struct st_pool_entry *pool;
242  unsigned int i;
243 
244  // Allocate an appropriately sized block from the heap. Include space for the pool header.
245  buffer=(uint8 *)calloc(1,sizeof(st_pool_entry)+sizeof(st_freelist_member)*allocation_step_itemcount);
246 
247  if (!buffer)
248  return;
249 
250  // Set the pointer to the pool header
251  pool=(st_pool_entry *)buffer;
252 
253  // Set the pointer to the first entry
254  workmember=(st_freelist_member *)(buffer+sizeof(st_pool_entry));
255  for (i=0;i<allocation_step_itemcount-1;i++)
256  {
257  // Set each entry (except the last) to point to the next extry
258  workmember->next=workmember+1;
259  workmember++;
260  }
261 
262  // Last entry should point to the head of the freelist
263  workmember->next=freelist_head;
264  // New freelist head should point to the first member
265  freelist_head=(st_freelist_member *)(buffer+sizeof(st_pool_entry));
266 
267  // Add this pool to the list of pools
268  pool->next=poollist_head;
269  poollist_head=pool;
270  }
271  else
272  {
273  uint8 *buffer;
274  TEMPLATE_CLASS *workclass;
275  struct st_pool_entry *pool;
276  unsigned int i;
277 
278  buffer=(uint8 *)malloc(sizeof(st_pool_entry)+sizeof(TEMPLATE_CLASS)*allocation_step_itemcount);
279 
280  if (!buffer)
281  return;
282 
283  // Junk memory to be on the safe side
284  memset(buffer, 0, sizeof(st_pool_entry));
285  memset(buffer+sizeof(st_pool_entry), 0xDD, sizeof(TEMPLATE_CLASS)*allocation_step_itemcount);
286 
287  // Set the pointer to the pool header
288  pool=(st_pool_entry *)buffer;
289 
290  // Set the pointer to the first entry
291  workclass=(TEMPLATE_CLASS *)(buffer+sizeof(st_pool_entry));
292  for (i=0;i<allocation_step_itemcount-1;i++)
293  {
294  // Set each entry (except the last) to point to the next extry
295  ((st_freelist_member *)workclass)->next=(st_freelist_member *)(workclass+1);
296  workclass++;
297  }
298 
299  // Last entry should point to the head of the freelist
300  ((st_freelist_member *)workclass)->next=freelist_head;
301 
302  // New freelist head should point to the first member
303  freelist_head=(st_freelist_member *)(buffer+sizeof(st_pool_entry));
304 
305  // Add this pool to the list of pools
306  pool->next=poollist_head;
307  poollist_head=pool;
308  }
309 
310  }
311 
312 
313 
314 };
315 
318 #endif
void CallFromDelete(TEMPLATE_CLASS *obj)
Places a block back on the list of available blocks. Does NOT release memory back to the heap - ever...
#define POOLALLOC_DEFAULT_ALLOCATION_OBJECTS
Definition: poolallocator.h:75
PoolAllocator(int items_per_pool=POOLALLOC_DEFAULT_ALLOCATION_OBJECTS)
PoolAllocator constructor. Sets list heads to NULL and the per-pool object count to the passed value ...
bool PointerCameFromPool(void *ptr)
TEMPLATE_CLASS * CallFromNew()
Returns a new block from the list of available blocks. Allocates another pool if necessary.
#define CS_ASSERT_MSG(msg, x)
Definition: loader.h:56
~PoolAllocator()
PoolAllocator destructor. Releases the memory assigned to the pools back to the heap and clears the p...