/* * poolallocator.h * * Copyright (C) 2001 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation (version 2 of the License) * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * A template class used to allocate fixed size objects in pools. * */ /* Design notes: * * This template is designed to be integrated into a class definition. * Override the new and delete operators for the class to call CallFromNew() and * CallFromDelete() respectively. * Add a private static member variable to the class declaration like so: * static PoolAllocator mypool; * * Add the definition (usually in the .cpp file): * PoolAllocator myclass::mypool(number of objects to allocate per "step"); * * Use new and delete as normal throughout the program. * * DO NOT DERIVE FROM A CLASS USING A POOL ALLOCATOR UNLESS YOU DO THE FOLLOWING: * 1) Create a new static PoolAllocator<> member for the derived class. * 2) Implement the new and delete operators as above using the new PoolAllocator<> member. * * Benefits: * Pool allocation using this method is very fast. It usually involves a test, an assignment * and a return. No mutex locking is needed since this is not an attempt at being thread safe. * * Limitations: * 1) Each pool operates on fixed-size objects. These have to be at least the size of struct * st_freelist_member (currently a single pointer. Which is 4 bytes on 32 bit platforms.) * or the size of each object is bumped up to this size (wasting memory). * 2) Memory is never returned to the OS until the entire pool is destructed. In the suggested * implementation this is at program exit. * 3) Allocation time is not constant (it isn't on the heap either). When the pool empties more * space is allocated from the heap and the pool is filled. The larger the allocation step * size (specified as a parameter to the constructor) the longer this process takes but the * process will have to be done less often. * 4) THIS IS NOT THREADSAFE!! To make this threadsafe the ideal solution would be atomic * operations (there is a nice paper on using atomic ops to safely handle linked lists * out there.) An alternative is to mutex most calls - which further reduces the benefit * over heap allocation. * * - A pool size of 3 should be about as efficient as normal heap allocation functions * */ #ifndef PS_POOLALLOCATOR_H #define PS_POOLALLOCATOR_H // Define this to enable testing to be sure a Release()'d object came from the pool to begin with (debug mode only) //#define POOLALLOC_DEBUG_DELETE #include #define POOLALLOC_DEFAULT_ALLOCATION_OBJECTS 1024 template class PoolAllocator { csRef mutex; /// A structure used as a header for each allocation "pool" struct st_pool_entry { struct st_pool_entry *next; }; /** A structure used to track free blocks. * * This structure uses memory inside the block itself, so the block must be * at least as large as this structure. */ struct st_freelist_member { struct st_freelist_member *next; }; /// Pointer to the first struct st_freelist_member in the freelist. The 'head'. struct st_freelist_member *freelist_head; /// Pointer to the first struct st_pool_entry in the list of all pools allocated. struct st_pool_entry *poollist_head; /** Contains the number of object-size blocks to allocate in each heap allocation. * * Can also be used (along with structure sizes) to calculate the size of pools. * Set by a parameter passed to the constructor. */ uint32 allocation_step_itemcount; public: /// PoolAllocator constructor. Sets list heads to NULL and the per-pool object count to the passed value (or default). PoolAllocator(int items_per_pool=POOLALLOC_DEFAULT_ALLOCATION_OBJECTS) { allocation_step_itemcount = items_per_pool; freelist_head = NULL; poollist_head = NULL; mutex = csMutex::Create(); } /// PoolAllocator destructor. Releases the memory assigned to the pools back to the heap and clears the pool and free lists. ~PoolAllocator() { struct st_pool_entry *pool=poollist_head,*nextpool; // Step through each pool, freeing the memory associated while (pool) { nextpool=pool->next; free(pool); pool=nextpool; } poollist_head=NULL; freelist_head=NULL; } /// Returns a new block from the list of available blocks. Allocates another pool if necessary. TEMPLATE_CLASS *CallFromNew() { CS_ASSERT_MSG("Contention detected in PoolAllocator new", mutex->LockTry()); TEMPLATE_CLASS *objptr; // Allocate another pool if no free blocks are available if (!freelist_head) AllocNewPool(); // Test for new pool allocation failure if (!freelist_head) { mutex->Release(); return (TEMPLATE_CLASS *)NULL; } // Bump a block off the free list and return it objptr=(TEMPLATE_CLASS *)freelist_head; freelist_head=freelist_head->next; mutex->Release(); return objptr; } /// Places a block back on the list of available blocks. Does NOT release memory back to the heap - ever. void CallFromDelete(TEMPLATE_CLASS *obj) { CS_ASSERT_MSG("Contention detected in PoolAllocator delete", mutex->LockTry()); // NULL pointers are OK - just like delete. if (!obj) { mutex->Release(); return; } #ifdef POOLALLOC_DEBUG_DELETE // Throw an assert if this block didn't come from this PoolAllocator CS_ASSERT(ObjectCameFromPool(obj)); #endif // Clear memory to be on the safe side memset(obj, 0xDD, sizeof(TEMPLATE_CLASS)); // Add this entry to the head of the free list ((struct st_freelist_member *)obj)->next=freelist_head; freelist_head=((struct st_freelist_member *)obj); mutex->Release(); } bool PointerCameFromPool(void *ptr) { return (ObjectCameFromPool((TEMPLATE_CLASS *)ptr)!=NULL); } private: /// Internal function. Checks if a given block came from one of the pools associated with this PoolAllocator struct st_pool_entry *ObjectCameFromPool(TEMPLATE_CLASS *obj) { int entrysize; struct st_pool_entry *testentry=poollist_head; // Determine the size of blocks entrysize=sizeof(TEMPLATE_CLASS); if (entrysize(uint8 *)testentry && (uint8 *)obj<(uint8 *)testentry + sizeof(st_pool_entry)+entrysize*allocation_step_itemcount) { if ((unsigned long)((uint8 *)obj-((uint8 *)testentry + sizeof(st_pool_entry))) % (unsigned long)entrysize) { // This pointer is within a pool, but does not fall on an object boundary! return NULL; } return testentry; } testentry=testentry->next; } return NULL; } /// Internal function. Allocates and prepares another pool of blocks for use. void AllocNewPool() { // Different allocation methods depending on the size of the class if (sizeof(TEMPLATE_CLASS)next=workmember+1; workmember++; } // Last entry should point to the head of the freelist workmember->next=freelist_head; // New freelist head should point to the first member freelist_head=(st_freelist_member *)(buffer+sizeof(st_pool_entry)); // Add this pool to the list of pools pool->next=poollist_head; poollist_head=pool; } else { uint8 *buffer; TEMPLATE_CLASS *workclass; struct st_pool_entry *pool; unsigned int i; buffer=(uint8 *)calloc(1,sizeof(st_pool_entry)+sizeof(TEMPLATE_CLASS)*allocation_step_itemcount); if (!buffer) return; // Set the pointer to the pool header pool=(st_pool_entry *)buffer; // Set the pointer to the first entry workclass=(TEMPLATE_CLASS *)(buffer+sizeof(st_pool_entry)); for (i=0;inext=(st_freelist_member *)(workclass+1); workclass++; } // Last entry should point to the head of the freelist ((st_freelist_member *)workclass)->next=freelist_head; // New freelist head should point to the first member freelist_head=(st_freelist_member *)(buffer+sizeof(st_pool_entry)); // Add this pool to the list of pools pool->next=poollist_head; poollist_head=pool; } } }; #endif