Browse Source

Feature: Added a pool allocator for fast allocation/deallocation of objects of equal size

BearishSun 8 years ago
parent
commit
27387db5bd

+ 228 - 0
Source/BansheeUtility/Allocators/BsPoolAlloc.h

@@ -0,0 +1,228 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2017 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "Prerequisites/BsPrerequisitesUtil.h"
+
+namespace bs
+{
+	/** @addtogroup Internal-Utility
+	 *  @{
+	 */
+
+	/** @addtogroup Memory-Internal
+	 *  @{
+	 */
+
+	/**
+	 * A memory allocator that allocates elements of the same size. Allows for fairly quick allocations and deallocations.
+	 * 
+	 * @tparam	ElemSize		Size of a single element in the pool. This will be the exact allocation size. 4 byte minimum.
+	 * @tparam	ElemsPerBlock	Determines how much space to reserve for elements. This determines the initial size of the
+	 *							pool, and the additional size the pool will be expanded by every time the number of elements
+	 *							goes over the available storage limit.
+	 * @tparam	Alignment		Memory alignment of each allocated element. Note that alignments that are larger than
+	 *							element size, or aren't a multiplier of element size will introduce additionally padding
+	 *							for each element, and therefore require more internal memory.
+	 */
+	template <int ElemSize, int ElemsPerBlock = 512, int Alignment = 4>
+	class PoolAlloc
+	{
+	private:
+		/** A single block able to hold ElemsPerBlock elements. */
+		class MemBlock
+		{
+		public:
+			MemBlock(UINT8* blockData)
+				:blockData(blockData), freePtr(0), freeElems(ElemsPerBlock), nextBlock(nullptr)
+			{
+				UINT32 offset = 0;
+				for(UINT32 i = 0; i < ElemsPerBlock; i++)
+				{
+					UINT32* entryPtr = (UINT32*)&blockData[offset];
+
+					offset += ActualElemSize;
+					*entryPtr = offset;
+				}
+			}
+
+			~MemBlock()
+			{
+				assert(freeElems == ElemsPerBlock && "Not all elements were deallocated from a block.");
+			}
+
+			/**
+			 * Returns the first free address and increments the free pointer. Caller needs to ensure the remaining block 
+			 * size is adequate before calling.
+			 */
+			UINT8* alloc()
+			{
+				UINT8* freeEntry = &blockData[freePtr];
+				freePtr = *(UINT32*)freeEntry;
+				--freeElems;
+
+				return freeEntry;
+			}
+
+			/** Deallocates the provided pointer. */
+			void dealloc(void* data)
+			{
+				UINT32* entryPtr = (UINT32*)data;
+				*entryPtr = freePtr;
+				++freeElems;
+
+				freePtr = (UINT32)(((UINT8*)data) - blockData);
+			}
+
+			UINT8* blockData;
+			UINT32 freePtr;
+			UINT32 freeElems;
+			MemBlock* nextBlock;
+		};
+
+	public:
+		PoolAlloc()
+			: mFreeBlock(nullptr), mTotalNumElems(0), mNumBlocks(0)
+		{
+			static_assert(ElemSize >= 4, "Pool allocator minimum allowed element size is 4 bytes.");
+			static_assert(ElemsPerBlock > 0, "Number of elements per block must be at least 1.");
+			static_assert(ElemsPerBlock * ActualElemSize <= UINT_MAX, "Pool allocator block size too large.");
+		}
+
+		~PoolAlloc()
+		{
+			MemBlock* curBlock = mFreeBlock;
+			while (curBlock != nullptr)
+			{
+				MemBlock* nextBlock = curBlock->nextBlock;
+				deallocBlock(curBlock);
+
+				curBlock = nextBlock;
+			}
+		}
+
+		/** Allocates enough memory for a single element in the pool. */
+		UINT8* alloc()
+		{
+			if(mFreeBlock == nullptr || mFreeBlock->freeElems == 0)
+				allocBlock();
+
+			mTotalNumElems++;
+			return mFreeBlock->alloc();
+		}
+
+		/** Deallocates an element from the pool. */
+		void free(void* data)
+		{
+			MemBlock* curBlock = mFreeBlock;
+			while(curBlock)
+			{
+				constexpr UINT32 blockDataSize = ActualElemSize * ElemsPerBlock;
+				if(data >= curBlock->blockData && data < (curBlock->blockData + blockDataSize))
+				{
+					curBlock->dealloc(data);
+					mTotalNumElems--;
+
+					if(curBlock->freeElems == 0 && curBlock->nextBlock)
+					{
+						// Free the block, but only if there is some extra free space in other blocks
+						const UINT32 totalSpace = (mNumBlocks - 1) * ElemsPerBlock;
+						const UINT32 freeSpace = totalSpace - mTotalNumElems;
+
+						if(freeSpace > ElemsPerBlock / 2)
+						{
+							mFreeBlock = curBlock->nextBlock;
+							deallocBlock(curBlock);
+						}
+					}
+
+					return;
+				}
+
+				curBlock = curBlock->nextBlock;
+			}
+
+			assert(false);
+		}
+
+		/** Allocates and constructs a single pool element. */
+		template<class T, class... Args>
+		T* construct(Args &&...args)
+		{
+			T* data = (T*)alloc();
+			new ((void*)data) T(std::forward<Args>(args)...);
+
+			return data;
+		}
+
+		/** Destructs and deallocates a single pool element. */
+		template<class T>
+		void destruct(T* data)
+		{
+			data->~T();
+			free(data);
+		}
+
+	private:
+		/** Allocates a new block of memory using a heap allocator. */
+		MemBlock* allocBlock()
+		{
+			MemBlock* newBlock = nullptr;
+			MemBlock* curBlock = mFreeBlock;
+
+			while (curBlock != nullptr)
+			{
+				MemBlock* nextBlock = curBlock->nextBlock;
+				if (nextBlock != nullptr && nextBlock->freeElems > 0)
+				{
+					// Found an existing block with free space
+					newBlock = nextBlock;
+
+					curBlock->nextBlock = newBlock->nextBlock;
+					newBlock->nextBlock = mFreeBlock;
+
+					break;
+				}
+
+				curBlock = nextBlock;
+			}
+
+			if (newBlock == nullptr)
+			{
+				constexpr UINT32 blockDataSize = ActualElemSize * ElemsPerBlock;
+				size_t paddedBlockDataSize = blockDataSize + (Alignment - 1); // Padding for potential alignment correction
+
+				UINT8* data = (UINT8*)bs_alloc(sizeof(MemBlock) + (UINT32)paddedBlockDataSize);
+
+				void* blockData = data + sizeof(MemBlock);
+				blockData = std::align(Alignment, blockDataSize, blockData, paddedBlockDataSize);
+
+				newBlock = new (data) MemBlock((UINT8*)blockData);
+				mNumBlocks++;
+
+				newBlock->nextBlock = mFreeBlock;
+			}
+
+			mFreeBlock = newBlock;
+			return newBlock;
+		}
+
+		/** Deallocates a block of memory. */
+		void deallocBlock(MemBlock* block)
+		{
+			block->~MemBlock();
+			bs_free(block);
+
+			mNumBlocks--;
+		}
+
+		static constexpr int ActualElemSize = ((ElemSize + Alignment - 1) / Alignment) * Alignment;
+
+		MemBlock* mFreeBlock;
+		UINT32 mTotalNumElems;
+		UINT32 mNumBlocks;
+	};
+
+	/** @} */
+	/** @} */
+}

+ 1 - 0
Source/BansheeUtility/CMakeSources.cmake

@@ -126,6 +126,7 @@ set(BS_BANSHEEUTILITY_INC_ALLOCATORS
 	"Allocators/BsStaticAlloc.h"
 	"Allocators/BsGroupAlloc.h"
 	"Allocators/BsFreeAlloc.h"
+	"Allocators/BsPoolAlloc.h"
 )
 
 set(BS_BANSHEEUTILITY_INC_THIRDPARTY