CpuMemoryPools.h 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. // Copyright (C) 2009-2022, 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. void retain() const
  54. {
  55. m_refcount.fetchAdd(1);
  56. }
  57. I32 release() const
  58. {
  59. return m_refcount.fetchSub(1);
  60. }
  61. /// Get number of users.
  62. U32 getUsersCount() const
  63. {
  64. return m_refcount.load();
  65. }
  66. /// Get allocation callback.
  67. AllocAlignedCallback getAllocationCallback() const
  68. {
  69. return m_allocCb;
  70. }
  71. /// Get allocation callback user data.
  72. void* getAllocationCallbackUserData() const
  73. {
  74. return m_allocCbUserData;
  75. }
  76. /// Return number of allocations
  77. U32 getAllocationCount() const
  78. {
  79. return m_allocationCount.load();
  80. }
  81. /// Get the name of the pool.
  82. const Char* getName() const
  83. {
  84. return (m_name) ? m_name : "Unamed";
  85. }
  86. protected:
  87. /// Pool type.
  88. enum class Type : U8
  89. {
  90. kNone,
  91. kHeap,
  92. kStack,
  93. };
  94. /// User allocation function.
  95. AllocAlignedCallback m_allocCb = nullptr;
  96. /// User allocation function data.
  97. void* m_allocCbUserData = nullptr;
  98. /// Allocations count.
  99. Atomic<U32> m_allocationCount = {0};
  100. BaseMemoryPool(Type type)
  101. : m_type(type)
  102. {
  103. }
  104. void init(AllocAlignedCallback allocCb, void* allocCbUserData, const Char* name);
  105. void destroy();
  106. private:
  107. /// Refcount.
  108. mutable Atomic<I32> m_refcount = {0};
  109. /// Optional name.
  110. char* m_name = nullptr;
  111. /// Type.
  112. Type m_type = Type::kNone;
  113. };
  114. /// A dummy interface to match the StackMemoryPool interfaces in order to be used by the same allocator template.
  115. class HeapMemoryPool final : public BaseMemoryPool
  116. {
  117. public:
  118. /// Construct it.
  119. HeapMemoryPool()
  120. : BaseMemoryPool(Type::kHeap)
  121. {
  122. }
  123. /// @see init
  124. HeapMemoryPool(AllocAlignedCallback allocCb, void* allocCbUserData, const Char* name = nullptr)
  125. : HeapMemoryPool()
  126. {
  127. init(allocCb, allocCbUserData, name);
  128. }
  129. /// Destroy
  130. ~HeapMemoryPool()
  131. {
  132. destroy();
  133. }
  134. /// Init.
  135. /// @param allocCb The allocation function callback.
  136. /// @param allocCbUserData The user data to pass to the allocation function.
  137. /// @param name An optional name.
  138. void init(AllocAlignedCallback allocCb, void* allocCbUserData, const Char* name = nullptr);
  139. /// Manual destroy. The destructor calls that as well.
  140. void destroy();
  141. /// Allocate memory
  142. void* allocate(PtrSize size, PtrSize alignment);
  143. /// Free memory.
  144. /// @param[in, out] ptr Memory block to deallocate.
  145. void free(void* ptr);
  146. private:
  147. #if ANKI_MEM_EXTRA_CHECKS
  148. PoolSignature m_signature = 0;
  149. #endif
  150. };
  151. /// Thread safe memory pool. It's a preallocated memory pool that is used for memory allocations on top of that
  152. /// preallocated memory. It is mainly used by fast stack allocators
  153. class StackMemoryPool final : public BaseMemoryPool
  154. {
  155. public:
  156. StackMemoryPool()
  157. : BaseMemoryPool(Type::kStack)
  158. {
  159. }
  160. /// @see init
  161. StackMemoryPool(AllocAlignedCallback allocCb, void* allocCbUserData, PtrSize initialChunkSize,
  162. F64 nextChunkScale = 2.0, PtrSize nextChunkBias = 0, Bool ignoreDeallocationErrors = true,
  163. U32 alignmentBytes = ANKI_SAFE_ALIGNMENT, const Char* name = nullptr)
  164. : StackMemoryPool()
  165. {
  166. init(allocCb, allocCbUserData, initialChunkSize, nextChunkScale, nextChunkBias, ignoreDeallocationErrors,
  167. alignmentBytes, name);
  168. }
  169. /// Destroy
  170. ~StackMemoryPool()
  171. {
  172. destroy();
  173. }
  174. /// Init with parameters.
  175. /// @param allocCb The allocation function callback.
  176. /// @param allocCbUserData The user data to pass to the allocation function.
  177. /// @param initialChunkSize The size of the first chunk.
  178. /// @param nextChunkScale Value that controls the next chunk.
  179. /// @param nextChunkBias Value that controls the next chunk.
  180. /// @param ignoreDeallocationErrors Method free() may fail if the ptr is not in the top of the stack. Set that to
  181. /// true to suppress such errors.
  182. /// @param alignmentBytes The maximum supported alignment for returned memory.
  183. /// @param name An optional name.
  184. void init(AllocAlignedCallback allocCb, void* allocCbUserData, PtrSize initialChunkSize, F64 nextChunkScale = 2.0,
  185. PtrSize nextChunkBias = 0, Bool ignoreDeallocationErrors = true, U32 alignmentBytes = ANKI_SAFE_ALIGNMENT,
  186. const Char* name = nullptr);
  187. /// Manual destroy. The destructor calls that as well.
  188. void destroy();
  189. /// Allocate aligned memory.
  190. /// @param size The size to allocate.
  191. /// @param alignmentBytes The alignment of the returned address.
  192. /// @return The allocated memory or nullptr on failure.
  193. ///
  194. /// @note The operation is thread safe with allocate() and free() methods.
  195. void* allocate(PtrSize size, PtrSize alignmentBytes);
  196. /// Free memory in StackMemoryPool. It will not actually do anything.
  197. /// @param[in, out] ptr Memory block to deallocate.
  198. void free(void* ptr);
  199. /// Reinit the pool. All existing allocated memory is effectively invalidated.
  200. /// @note It's not thread safe with other methods.
  201. void reset();
  202. /// Get the physical memory allocated by the pool.
  203. /// @note It's not thread safe with other methods.
  204. PtrSize getMemoryCapacity() const
  205. {
  206. return m_builder.getMemoryCapacity();
  207. }
  208. private:
  209. /// This is the absolute max alignment.
  210. static constexpr U32 kMaxAlignment = ANKI_SAFE_ALIGNMENT;
  211. /// This is the chunk the StackAllocatorBuilder will be allocating.
  212. class alignas(kMaxAlignment) Chunk
  213. {
  214. public:
  215. /// Required by StackAllocatorBuilder.
  216. Chunk* m_nextChunk;
  217. /// Required by StackAllocatorBuilder.
  218. Atomic<PtrSize> m_offsetInChunk;
  219. /// Required by StackAllocatorBuilder.
  220. PtrSize m_chunkSize;
  221. /// The start of the actual CPU memory.
  222. alignas(kMaxAlignment) U8 m_memoryStart[1];
  223. };
  224. /// Implements the StackAllocatorBuilder TInterface
  225. class StackAllocatorBuilderInterface
  226. {
  227. public:
  228. StackMemoryPool* m_parent = nullptr;
  229. PtrSize m_alignmentBytes = 0;
  230. Bool m_ignoreDeallocationErrors = false;
  231. PtrSize m_initialChunkSize = 0;
  232. F64 m_nextChunkScale = 0.0;
  233. PtrSize m_nextChunkBias = 0;
  234. // The rest of the functions implement the StackAllocatorBuilder TInterface.
  235. PtrSize getMaxAlignment() const
  236. {
  237. ANKI_ASSERT(m_alignmentBytes > 0);
  238. return m_alignmentBytes;
  239. }
  240. PtrSize getInitialChunkSize() const
  241. {
  242. ANKI_ASSERT(m_initialChunkSize > 0);
  243. return m_initialChunkSize;
  244. }
  245. F64 getNextChunkGrowScale() const
  246. {
  247. ANKI_ASSERT(m_nextChunkScale >= 1.0);
  248. return m_nextChunkScale;
  249. }
  250. PtrSize getNextChunkGrowBias() const
  251. {
  252. return m_nextChunkBias;
  253. }
  254. Bool ignoreDeallocationErrors() const
  255. {
  256. return m_ignoreDeallocationErrors;
  257. }
  258. Error allocateChunk(PtrSize size, Chunk*& out);
  259. void freeChunk(Chunk* chunk);
  260. void recycleChunk(Chunk& chunk);
  261. Atomic<U32>* getAllocationCount()
  262. {
  263. return (m_parent) ? &m_parent->m_allocationCount : nullptr;
  264. }
  265. };
  266. /// The allocator helper.
  267. StackAllocatorBuilder<Chunk, StackAllocatorBuilderInterface, Mutex> m_builder;
  268. };
  269. /// A wrapper class that makes a pointer to a memory pool act like a reference.
  270. template<typename TMemPool>
  271. class MemoryPoolPtrWrapper
  272. {
  273. public:
  274. TMemPool* m_pool = nullptr;
  275. MemoryPoolPtrWrapper() = default;
  276. MemoryPoolPtrWrapper(TMemPool* pool)
  277. : m_pool(pool)
  278. {
  279. ANKI_ASSERT(pool);
  280. }
  281. TMemPool* operator&()
  282. {
  283. ANKI_ASSERT(m_pool);
  284. return m_pool;
  285. }
  286. operator TMemPool&()
  287. {
  288. ANKI_ASSERT(m_pool);
  289. return *m_pool;
  290. }
  291. void* allocate(PtrSize size, PtrSize alignmentBytes)
  292. {
  293. ANKI_ASSERT(m_pool);
  294. return m_pool->allocate(size, alignmentBytes);
  295. }
  296. void free(void* ptr)
  297. {
  298. ANKI_ASSERT(m_pool);
  299. m_pool->free(ptr);
  300. }
  301. };
  302. inline void* BaseMemoryPool::allocate(PtrSize size, PtrSize alignmentBytes)
  303. {
  304. void* out = nullptr;
  305. switch(m_type)
  306. {
  307. case Type::kHeap:
  308. out = static_cast<HeapMemoryPool*>(this)->allocate(size, alignmentBytes);
  309. break;
  310. case Type::kStack:
  311. out = static_cast<StackMemoryPool*>(this)->allocate(size, alignmentBytes);
  312. break;
  313. default:
  314. ANKI_ASSERT(0);
  315. }
  316. return out;
  317. }
  318. inline void BaseMemoryPool::free(void* ptr)
  319. {
  320. switch(m_type)
  321. {
  322. case Type::kHeap:
  323. static_cast<HeapMemoryPool*>(this)->free(ptr);
  324. break;
  325. case Type::kStack:
  326. static_cast<StackMemoryPool*>(this)->free(ptr);
  327. break;
  328. default:
  329. ANKI_ASSERT(0);
  330. }
  331. }
  332. /// @}
  333. } // end namespace anki