BsPoolAlloc.h 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2017 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #pragma once
  4. #include "Prerequisites/BsPrerequisitesUtil.h"
  5. #include <climits>
  6. namespace bs
  7. {
  8. /** @addtogroup Internal-Utility
  9. * @{
  10. */
  11. /** @addtogroup Memory-Internal
  12. * @{
  13. */
  14. /**
  15. * A memory allocator that allocates elements of the same size. Allows for fairly quick allocations and deallocations.
  16. *
  17. * @tparam ElemSize Size of a single element in the pool. This will be the exact allocation size. 4 byte minimum.
  18. * @tparam ElemsPerBlock Determines how much space to reserve for elements. This determines the initial size of the
  19. * pool, and the additional size the pool will be expanded by every time the number of elements
  20. * goes over the available storage limit.
  21. * @tparam Alignment Memory alignment of each allocated element. Note that alignments that are larger than
  22. * element size, or aren't a multiplier of element size will introduce additionally padding
  23. * for each element, and therefore require more internal memory.
  24. */
  25. template <int ElemSize, int ElemsPerBlock = 512, int Alignment = 4>
  26. class PoolAlloc
  27. {
  28. private:
  29. /** A single block able to hold ElemsPerBlock elements. */
  30. class MemBlock
  31. {
  32. public:
  33. MemBlock(UINT8* blockData)
  34. :blockData(blockData), freePtr(0), freeElems(ElemsPerBlock), nextBlock(nullptr)
  35. {
  36. UINT32 offset = 0;
  37. for(UINT32 i = 0; i < ElemsPerBlock; i++)
  38. {
  39. UINT32* entryPtr = (UINT32*)&blockData[offset];
  40. offset += ActualElemSize;
  41. *entryPtr = offset;
  42. }
  43. }
  44. ~MemBlock()
  45. {
  46. assert(freeElems == ElemsPerBlock && "Not all elements were deallocated from a block.");
  47. }
  48. /**
  49. * Returns the first free address and increments the free pointer. Caller needs to ensure the remaining block
  50. * size is adequate before calling.
  51. */
  52. UINT8* alloc()
  53. {
  54. UINT8* freeEntry = &blockData[freePtr];
  55. freePtr = *(UINT32*)freeEntry;
  56. --freeElems;
  57. return freeEntry;
  58. }
  59. /** Deallocates the provided pointer. */
  60. void dealloc(void* data)
  61. {
  62. UINT32* entryPtr = (UINT32*)data;
  63. *entryPtr = freePtr;
  64. ++freeElems;
  65. freePtr = (UINT32)(((UINT8*)data) - blockData);
  66. }
  67. UINT8* blockData;
  68. UINT32 freePtr;
  69. UINT32 freeElems;
  70. MemBlock* nextBlock;
  71. };
  72. public:
  73. PoolAlloc()
  74. : mFreeBlock(nullptr), mTotalNumElems(0), mNumBlocks(0)
  75. {
  76. static_assert(ElemSize >= 4, "Pool allocator minimum allowed element size is 4 bytes.");
  77. static_assert(ElemsPerBlock > 0, "Number of elements per block must be at least 1.");
  78. static_assert(ElemsPerBlock * ActualElemSize <= UINT_MAX, "Pool allocator block size too large.");
  79. }
  80. ~PoolAlloc()
  81. {
  82. MemBlock* curBlock = mFreeBlock;
  83. while (curBlock != nullptr)
  84. {
  85. MemBlock* nextBlock = curBlock->nextBlock;
  86. deallocBlock(curBlock);
  87. curBlock = nextBlock;
  88. }
  89. }
  90. /** Allocates enough memory for a single element in the pool. */
  91. UINT8* alloc()
  92. {
  93. if(mFreeBlock == nullptr || mFreeBlock->freeElems == 0)
  94. allocBlock();
  95. mTotalNumElems++;
  96. return mFreeBlock->alloc();
  97. }
  98. /** Deallocates an element from the pool. */
  99. void free(void* data)
  100. {
  101. MemBlock* curBlock = mFreeBlock;
  102. while(curBlock)
  103. {
  104. constexpr UINT32 blockDataSize = ActualElemSize * ElemsPerBlock;
  105. if(data >= curBlock->blockData && data < (curBlock->blockData + blockDataSize))
  106. {
  107. curBlock->dealloc(data);
  108. mTotalNumElems--;
  109. if(curBlock->freeElems == 0 && curBlock->nextBlock)
  110. {
  111. // Free the block, but only if there is some extra free space in other blocks
  112. const UINT32 totalSpace = (mNumBlocks - 1) * ElemsPerBlock;
  113. const UINT32 freeSpace = totalSpace - mTotalNumElems;
  114. if(freeSpace > ElemsPerBlock / 2)
  115. {
  116. mFreeBlock = curBlock->nextBlock;
  117. deallocBlock(curBlock);
  118. }
  119. }
  120. return;
  121. }
  122. curBlock = curBlock->nextBlock;
  123. }
  124. assert(false);
  125. }
  126. /** Allocates and constructs a single pool element. */
  127. template<class T, class... Args>
  128. T* construct(Args &&...args)
  129. {
  130. T* data = (T*)alloc();
  131. new ((void*)data) T(std::forward<Args>(args)...);
  132. return data;
  133. }
  134. /** Destructs and deallocates a single pool element. */
  135. template<class T>
  136. void destruct(T* data)
  137. {
  138. data->~T();
  139. free(data);
  140. }
  141. private:
  142. /** Allocates a new block of memory using a heap allocator. */
  143. MemBlock* allocBlock()
  144. {
  145. MemBlock* newBlock = nullptr;
  146. MemBlock* curBlock = mFreeBlock;
  147. while (curBlock != nullptr)
  148. {
  149. MemBlock* nextBlock = curBlock->nextBlock;
  150. if (nextBlock != nullptr && nextBlock->freeElems > 0)
  151. {
  152. // Found an existing block with free space
  153. newBlock = nextBlock;
  154. curBlock->nextBlock = newBlock->nextBlock;
  155. newBlock->nextBlock = mFreeBlock;
  156. break;
  157. }
  158. curBlock = nextBlock;
  159. }
  160. if (newBlock == nullptr)
  161. {
  162. constexpr UINT32 blockDataSize = ActualElemSize * ElemsPerBlock;
  163. size_t paddedBlockDataSize = blockDataSize + (Alignment - 1); // Padding for potential alignment correction
  164. UINT8* data = (UINT8*)bs_alloc(sizeof(MemBlock) + (UINT32)paddedBlockDataSize);
  165. void* blockData = data + sizeof(MemBlock);
  166. blockData = std::align(Alignment, blockDataSize, blockData, paddedBlockDataSize);
  167. newBlock = new (data) MemBlock((UINT8*)blockData);
  168. mNumBlocks++;
  169. newBlock->nextBlock = mFreeBlock;
  170. }
  171. mFreeBlock = newBlock;
  172. return newBlock;
  173. }
  174. /** Deallocates a block of memory. */
  175. void deallocBlock(MemBlock* block)
  176. {
  177. block->~MemBlock();
  178. bs_free(block);
  179. mNumBlocks--;
  180. }
  181. static constexpr int ActualElemSize = ((ElemSize + Alignment - 1) / Alignment) * Alignment;
  182. MemBlock* mFreeBlock;
  183. UINT32 mTotalNumElems;
  184. UINT32 mNumBlocks;
  185. };
  186. /** @} */
  187. /** @} */
  188. }