MemoryPool.h 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. // Copyright (C) 2009-2023, Panagiotis Christopoulos Charitos and contributors.
  2. // All rights reserved.
  3. // Code licensed under the BSD License.
  4. // http://www.anki3d.org/LICENSE
  5. #pragma once
  6. #include <AnKi/Util/StdTypes.h>
  7. #include <AnKi/Util/Atomic.h>
  8. #include <AnKi/Util/Assert.h>
  9. #include <AnKi/Util/Thread.h>
  10. #include <AnKi/Util/StackAllocatorBuilder.h>
  11. #include <utility> // For forward
  12. namespace anki {
  13. /// @addtogroup util_memory
  14. /// @{
  15. #define ANKI_MEM_EXTRA_CHECKS ANKI_EXTRA_CHECKS
  16. /// Allocate aligned memory
  17. void* mallocAligned(PtrSize size, PtrSize alignmentBytes);
  18. /// Free aligned memory
  19. void freeAligned(void* ptr);
  20. /// The function signature of a memory allocation/deallocation. See allocAligned function for the explanation of
  21. /// arguments
  22. using AllocAlignedCallback = void* (*)(void* userData, void* ptr, PtrSize size, PtrSize alignment);
  23. /// An internal type.
  24. using PoolSignature = U32;
  25. /// This is a function that allocates and deallocates heap memory. If the @a ptr is nullptr then it allocates using the
  26. /// @a size and @a alignment. If the @a ptr is not nullptr it deallocates the memory and the @a size and @a alignment is
  27. /// ignored.
  28. ///
  29. /// @param userData Used defined data
  30. /// @param ptr The pointer to deallocate or nullptr
  31. /// @param size The size to allocate or 0
  32. /// @param alignment The allocation alignment or 0
  33. /// @return On allocation mode it will return the newelly allocated block or nullptr on error. On deallocation mode
  34. /// returns nullptr
  35. void* allocAligned(void* userData, void* ptr, PtrSize size, PtrSize alignment);
  36. /// Generic memory pool. The base of HeapMemoryPool or StackMemoryPool.
  37. class BaseMemoryPool
  38. {
  39. public:
  40. BaseMemoryPool(const BaseMemoryPool&) = delete; // Non-copyable
  41. virtual ~BaseMemoryPool()
  42. {
  43. }
  44. BaseMemoryPool& operator=(const BaseMemoryPool&) = delete; // Non-copyable
  45. /// Allocate memory. This operation MAY be thread safe
  46. /// @param size The size to allocate
  47. /// @param alignmentBytes The alignment of the returned address
  48. /// @return The allocated memory or nullptr on failure
  49. void* allocate(PtrSize size, PtrSize alignmentBytes);
  50. /// Free memory.
  51. /// @param[in, out] ptr Memory block to deallocate
  52. void free(void* ptr);
  53. /// Get allocation callback.
  54. AllocAlignedCallback getAllocationCallback() const
  55. {
  56. return m_allocCb;
  57. }
  58. /// Get allocation callback user data.
  59. void* getAllocationCallbackUserData() const
  60. {
  61. return m_allocCbUserData;
  62. }
  63. /// Return number of allocations
  64. U32 getAllocationCount() const
  65. {
  66. return m_allocationCount.load();
  67. }
  68. /// Get the name of the pool.
  69. const Char* getName() const
  70. {
  71. return (m_name) ? m_name : "Unamed";
  72. }
  73. protected:
  74. /// Pool type.
  75. enum class Type : U8
  76. {
  77. kNone,
  78. kHeap,
  79. kStack,
  80. };
  81. /// User allocation function.
  82. AllocAlignedCallback m_allocCb = nullptr;
  83. /// User allocation function data.
  84. void* m_allocCbUserData = nullptr;
  85. /// Allocations count.
  86. Atomic<U32> m_allocationCount = {0};
  87. BaseMemoryPool(Type type)
  88. : m_type(type)
  89. {
  90. }
  91. void init(AllocAlignedCallback allocCb, void* allocCbUserData, const Char* name);
  92. void destroy();
  93. private:
  94. /// Optional name.
  95. char* m_name = nullptr;
  96. /// Type.
  97. Type m_type = Type::kNone;
  98. };
  99. /// A dummy interface to match the StackMemoryPool interfaces in order to be used by the same allocator template.
  100. class HeapMemoryPool : public BaseMemoryPool
  101. {
  102. public:
  103. /// Construct it.
  104. HeapMemoryPool()
  105. : BaseMemoryPool(Type::kHeap)
  106. {
  107. }
  108. /// @see init
  109. HeapMemoryPool(AllocAlignedCallback allocCb, void* allocCbUserData, const Char* name = nullptr)
  110. : HeapMemoryPool()
  111. {
  112. init(allocCb, allocCbUserData, name);
  113. }
  114. /// Destroy
  115. ~HeapMemoryPool()
  116. {
  117. destroy();
  118. }
  119. /// Init.
  120. /// @param allocCb The allocation function callback.
  121. /// @param allocCbUserData The user data to pass to the allocation function.
  122. /// @param name An optional name.
  123. void init(AllocAlignedCallback allocCb, void* allocCbUserData, const Char* name = nullptr);
  124. /// Manual destroy. The destructor calls that as well.
  125. void destroy();
  126. /// Allocate memory
  127. void* allocate(PtrSize size, PtrSize alignment);
  128. /// Free memory.
  129. /// @param[in, out] ptr Memory block to deallocate.
  130. void free(void* ptr);
  131. private:
  132. #if ANKI_MEM_EXTRA_CHECKS
  133. PoolSignature m_signature = 0;
  134. #endif
  135. };
  136. /// The default global memory pool.
  137. class DefaultMemoryPool : public HeapMemoryPool, public MakeSingleton<DefaultMemoryPool>
  138. {
  139. template<typename>
  140. friend class MakeSingleton;
  141. private:
  142. DefaultMemoryPool(AllocAlignedCallback allocCb, void* allocCbUserData)
  143. : HeapMemoryPool(allocCb, allocCbUserData, "DefaultMemPool")
  144. {
  145. }
  146. ~DefaultMemoryPool() = default;
  147. };
  148. /// Thread safe memory pool. It's a preallocated memory pool that is used for memory allocations on top of that
  149. /// preallocated memory. It is mainly used by fast stack allocators
  150. class StackMemoryPool : public BaseMemoryPool
  151. {
  152. public:
  153. StackMemoryPool()
  154. : BaseMemoryPool(Type::kStack)
  155. {
  156. }
  157. /// @see init
  158. StackMemoryPool(AllocAlignedCallback allocCb, void* allocCbUserData, PtrSize initialChunkSize,
  159. F64 nextChunkScale = 2.0, PtrSize nextChunkBias = 0, Bool ignoreDeallocationErrors = true,
  160. U32 alignmentBytes = ANKI_SAFE_ALIGNMENT, const Char* name = nullptr)
  161. : StackMemoryPool()
  162. {
  163. init(allocCb, allocCbUserData, initialChunkSize, nextChunkScale, nextChunkBias, ignoreDeallocationErrors,
  164. alignmentBytes, name);
  165. }
  166. /// Destroy
  167. ~StackMemoryPool()
  168. {
  169. destroy();
  170. }
  171. /// Init with parameters.
  172. /// @param allocCb The allocation function callback.
  173. /// @param allocCbUserData The user data to pass to the allocation function.
  174. /// @param initialChunkSize The size of the first chunk.
  175. /// @param nextChunkScale Value that controls the next chunk.
  176. /// @param nextChunkBias Value that controls the next chunk.
  177. /// @param ignoreDeallocationErrors Method free() may fail if the ptr is not in the top of the stack. Set that to
  178. /// true to suppress such errors.
  179. /// @param alignmentBytes The maximum supported alignment for returned memory.
  180. /// @param name An optional name.
  181. void init(AllocAlignedCallback allocCb, void* allocCbUserData, PtrSize initialChunkSize, F64 nextChunkScale = 2.0,
  182. PtrSize nextChunkBias = 0, Bool ignoreDeallocationErrors = true, U32 alignmentBytes = ANKI_SAFE_ALIGNMENT,
  183. const Char* name = nullptr);
  184. /// Manual destroy. The destructor calls that as well.
  185. void destroy();
  186. /// Allocate aligned memory.
  187. /// @param size The size to allocate.
  188. /// @param alignmentBytes The alignment of the returned address.
  189. /// @return The allocated memory or nullptr on failure.
  190. ///
  191. /// @note The operation is thread safe with allocate() and free() methods.
  192. void* allocate(PtrSize size, PtrSize alignmentBytes);
  193. /// Free memory in StackMemoryPool. It will not actually do anything.
  194. /// @param[in, out] ptr Memory block to deallocate.
  195. void free(void* ptr);
  196. /// Reinit the pool. All existing allocated memory is effectively invalidated.
  197. /// @note It's not thread safe with other methods.
  198. void reset();
  199. /// Get the physical memory allocated by the pool.
  200. /// @note It's not thread safe with other methods.
  201. PtrSize getMemoryCapacity() const
  202. {
  203. return m_builder.getMemoryCapacity();
  204. }
  205. private:
  206. /// This is the absolute max alignment.
  207. static constexpr U32 kMaxAlignment = ANKI_SAFE_ALIGNMENT;
  208. /// This is the chunk the StackAllocatorBuilder will be allocating.
  209. class alignas(kMaxAlignment) Chunk
  210. {
  211. public:
  212. /// Required by StackAllocatorBuilder.
  213. Chunk* m_nextChunk;
  214. /// Required by StackAllocatorBuilder.
  215. Atomic<PtrSize> m_offsetInChunk;
  216. /// Required by StackAllocatorBuilder.
  217. PtrSize m_chunkSize;
  218. /// The start of the actual CPU memory.
  219. alignas(kMaxAlignment) U8 m_memoryStart[1];
  220. };
  221. /// Implements the StackAllocatorBuilder TInterface
  222. class StackAllocatorBuilderInterface
  223. {
  224. public:
  225. StackMemoryPool* m_parent = nullptr;
  226. PtrSize m_alignmentBytes = 0;
  227. Bool m_ignoreDeallocationErrors = false;
  228. PtrSize m_initialChunkSize = 0;
  229. F64 m_nextChunkScale = 0.0;
  230. PtrSize m_nextChunkBias = 0;
  231. // The rest of the functions implement the StackAllocatorBuilder TInterface.
  232. PtrSize getMaxAlignment() const
  233. {
  234. ANKI_ASSERT(m_alignmentBytes > 0);
  235. return m_alignmentBytes;
  236. }
  237. PtrSize getInitialChunkSize() const
  238. {
  239. ANKI_ASSERT(m_initialChunkSize > 0);
  240. return m_initialChunkSize;
  241. }
  242. F64 getNextChunkGrowScale() const
  243. {
  244. ANKI_ASSERT(m_nextChunkScale >= 1.0);
  245. return m_nextChunkScale;
  246. }
  247. PtrSize getNextChunkGrowBias() const
  248. {
  249. return m_nextChunkBias;
  250. }
  251. Bool ignoreDeallocationErrors() const
  252. {
  253. return m_ignoreDeallocationErrors;
  254. }
  255. Error allocateChunk(PtrSize size, Chunk*& out);
  256. void freeChunk(Chunk* chunk);
  257. void recycleChunk(Chunk& chunk);
  258. Atomic<U32>* getAllocationCount()
  259. {
  260. return (m_parent) ? &m_parent->m_allocationCount : nullptr;
  261. }
  262. };
  263. /// The allocator helper.
  264. StackAllocatorBuilder<Chunk, StackAllocatorBuilderInterface, Mutex> m_builder;
  265. };
  266. /// A wrapper class that makes a pointer to a memory pool act like a reference.
  267. template<typename TMemPool>
  268. class MemoryPoolPtrWrapper
  269. {
  270. public:
  271. TMemPool* m_pool = nullptr;
  272. MemoryPoolPtrWrapper() = default;
  273. MemoryPoolPtrWrapper(TMemPool* pool)
  274. : m_pool(pool)
  275. {
  276. ANKI_ASSERT(pool);
  277. }
  278. TMemPool* operator&()
  279. {
  280. ANKI_ASSERT(m_pool);
  281. return m_pool;
  282. }
  283. operator TMemPool&()
  284. {
  285. ANKI_ASSERT(m_pool);
  286. return *m_pool;
  287. }
  288. void* allocate(PtrSize size, PtrSize alignmentBytes)
  289. {
  290. ANKI_ASSERT(m_pool);
  291. return m_pool->allocate(size, alignmentBytes);
  292. }
  293. void free(void* ptr)
  294. {
  295. ANKI_ASSERT(m_pool);
  296. m_pool->free(ptr);
  297. }
  298. };
  299. /// A wrapper class that adds a refcount to a memory pool.
  300. template<typename TMemPool>
  301. class RefCountedMemoryPool : public TMemPool
  302. {
  303. public:
  304. void retain() const
  305. {
  306. m_refcount.fetchAdd(1);
  307. }
  308. I32 release() const
  309. {
  310. return m_refcount.fetchSub(1);
  311. }
  312. private:
  313. mutable Atomic<I32> m_refcount = {0};
  314. };
  315. template<typename TMemoryPool>
  316. class SingletonMemoryPoolWrapper
  317. {
  318. public:
  319. TMemoryPool* operator&()
  320. {
  321. return &TMemoryPool::getSingleton();
  322. }
  323. operator TMemoryPool&()
  324. {
  325. return TMemoryPool::getSingleton();
  326. }
  327. void* allocate(PtrSize size, PtrSize alignmentBytes)
  328. {
  329. return TMemoryPool::getSingleton().allocate(size, alignmentBytes);
  330. }
  331. void free(void* ptr)
  332. {
  333. TMemoryPool::getSingleton().free(ptr);
  334. }
  335. };
  336. inline void* BaseMemoryPool::allocate(PtrSize size, PtrSize alignmentBytes)
  337. {
  338. void* out = nullptr;
  339. switch(m_type)
  340. {
  341. case Type::kHeap:
  342. out = static_cast<HeapMemoryPool*>(this)->allocate(size, alignmentBytes);
  343. break;
  344. case Type::kStack:
  345. out = static_cast<StackMemoryPool*>(this)->allocate(size, alignmentBytes);
  346. break;
  347. default:
  348. ANKI_ASSERT(0);
  349. }
  350. return out;
  351. }
  352. inline void BaseMemoryPool::free(void* ptr)
  353. {
  354. switch(m_type)
  355. {
  356. case Type::kHeap:
  357. static_cast<HeapMemoryPool*>(this)->free(ptr);
  358. break;
  359. case Type::kStack:
  360. static_cast<StackMemoryPool*>(this)->free(ptr);
  361. break;
  362. default:
  363. ANKI_ASSERT(0);
  364. }
  365. }
  366. /// @}
  367. } // end namespace anki