Diligent Engine  v.2.4.g
FixedBlockMemoryAllocator.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2019-2021 Diligent Graphics LLC
3  * Copyright 2015-2019 Egor Yusov
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  * In no event and under no legal theory, whether in tort (including negligence),
18  * contract, or otherwise, unless required by applicable law (such as deliberate
19  * and grossly negligent acts) or agreed to in writing, shall any Contributor be
20  * liable for any damages, including any direct, indirect, special, incidental,
21  * or consequential damages of any character arising as a result of this License or
22  * out of the use or inability to use the software (including but not limited to damages
23  * for loss of goodwill, work stoppage, computer failure or malfunction, or any and
24  * all other commercial damages or losses), even if such Contributor has been advised
25  * of the possibility of such damages.
26  */
27 
28 #pragma once
29 
32 
33 #include <unordered_map>
34 #include <mutex>
35 #include <unordered_set>
36 #include <vector>
37 #include <cstring>
38 #include <memory>
39 #include "../../Primitives/interface/Errors.hpp"
40 #include "../../Primitives/interface/MemoryAllocator.h"
41 #include "STDAllocator.hpp"
42 
43 namespace Diligent
44 {
45 
46 #ifdef DILIGENT_DEBUG
47 inline void FillWithDebugPattern(void* ptr, Uint8 Pattern, size_t NumBytes)
48 {
49  memset(ptr, Pattern, NumBytes);
50 }
51 #else
52 # define FillWithDebugPattern(...)
53 #endif
54 
57 {
58 public:
59  FixedBlockMemoryAllocator(IMemoryAllocator& RawMemoryAllocator, size_t BlockSize, Uint32 NumBlocksInPage);
61 
63  virtual void* Allocate(size_t Size, const Char* dbgDescription, const char* dbgFileName, const Int32 dbgLineNumber) override final;
64 
66  virtual void Free(void* Ptr) override final;
67 
68 private:
69  // clang-format off
72  FixedBlockMemoryAllocator& operator = (const FixedBlockMemoryAllocator&) = delete;
74  // clang-format on
75 
76  void CreateNewPage();
77 
78  // Memory page class is based on the fixed-size memory pool described in "Fast Efficient Fixed-Size Memory Pool"
79  // by Ben Kenwright
80  class MemoryPage
81  {
82  public:
83  static constexpr Uint8 NewPageMemPattern = 0xAA;
84  static constexpr Uint8 AllocatedBlockMemPattern = 0xAB;
85  static constexpr Uint8 DeallocatedBlockMemPattern = 0xDE;
86  static constexpr Uint8 InitializedBlockMemPattern = 0xCF;
87 
88  MemoryPage(FixedBlockMemoryAllocator& OwnerAllocator) :
89  // clang-format off
90  m_NumFreeBlocks {OwnerAllocator.m_NumBlocksInPage},
91  m_NumInitializedBlocks{0},
92  m_pOwnerAllocator {&OwnerAllocator}
93  // clang-format on
94  {
95  auto PageSize = OwnerAllocator.m_BlockSize * OwnerAllocator.m_NumBlocksInPage;
96  m_pPageStart = reinterpret_cast<Uint8*>(
97  OwnerAllocator.m_RawMemoryAllocator.Allocate(PageSize, "FixedBlockMemoryAllocator page", __FILE__, __LINE__));
98  m_pNextFreeBlock = m_pPageStart;
99  FillWithDebugPattern(m_pPageStart, NewPageMemPattern, PageSize);
100  }
101 
102  MemoryPage(MemoryPage&& Page) noexcept :
103  // clang-format off
104  m_NumFreeBlocks {Page.m_NumFreeBlocks },
105  m_NumInitializedBlocks{Page.m_NumInitializedBlocks},
106  m_pPageStart {Page.m_pPageStart },
107  m_pNextFreeBlock {Page.m_pNextFreeBlock },
108  m_pOwnerAllocator {Page.m_pOwnerAllocator }
109  // clang-format on
110  {
111  Page.m_NumFreeBlocks = 0;
112  Page.m_NumInitializedBlocks = 0;
113  Page.m_pPageStart = nullptr;
114  Page.m_pNextFreeBlock = nullptr;
115  Page.m_pOwnerAllocator = nullptr;
116  }
117 
118  ~MemoryPage()
119  {
120  if (m_pOwnerAllocator)
121  m_pOwnerAllocator->m_RawMemoryAllocator.Free(m_pPageStart);
122  }
123 
124  void* GetBlockStartAddress(Uint32 BlockIndex) const
125  {
126  VERIFY_EXPR(m_pOwnerAllocator != nullptr);
127  VERIFY(BlockIndex >= 0 && BlockIndex < m_pOwnerAllocator->m_NumBlocksInPage, "Invalid block index");
128  return reinterpret_cast<Uint8*>(m_pPageStart) + BlockIndex * m_pOwnerAllocator->m_BlockSize;
129  }
130 
131 #ifdef DILIGENT_DEBUG
132  void dbgVerifyAddress(const void* pBlockAddr) const
133  {
134  size_t Delta = reinterpret_cast<const Uint8*>(pBlockAddr) - reinterpret_cast<Uint8*>(m_pPageStart);
135  VERIFY(Delta % m_pOwnerAllocator->m_BlockSize == 0, "Invalid address");
136  Uint32 BlockIndex = static_cast<Uint32>(Delta / m_pOwnerAllocator->m_BlockSize);
137  VERIFY(BlockIndex >= 0 && BlockIndex < m_pOwnerAllocator->m_NumBlocksInPage, "Invalid block index");
138  }
139 #else
140 # define dbgVerifyAddress(...)
141 #endif
142 
143  void* Allocate()
144  {
145  VERIFY_EXPR(m_pOwnerAllocator != nullptr);
146 
147  if (m_NumFreeBlocks == 0)
148  {
149  VERIFY_EXPR(m_NumInitializedBlocks == m_pOwnerAllocator->m_NumBlocksInPage);
150  return nullptr;
151  }
152 
153  // Initialize the next block
154  if (m_NumInitializedBlocks < m_pOwnerAllocator->m_NumBlocksInPage)
155  {
156  // Link next uninitialized block to the end of the list:
157 
158  //
159  // ___________ ___________
160  // | | | |
161  // | 0xcdcdcd | -->| 0xcdcdcd | m_NumInitializedBlocks
162  // |-----------| | |-----------|
163  // | | | | |
164  // m_NumInitializedBlocks | 0xcdcdcd | ==> ---| |
165  // |-----------| |-----------|
166  //
167  // ~ ~ ~ ~
168  // | | | |
169  // 0 | | | |
170  // ----------- -----------
171  //
172  auto* pUninitializedBlock = GetBlockStartAddress(m_NumInitializedBlocks);
173  FillWithDebugPattern(pUninitializedBlock, InitializedBlockMemPattern, m_pOwnerAllocator->m_BlockSize);
174  void** ppNextBlock = reinterpret_cast<void**>(pUninitializedBlock);
175  ++m_NumInitializedBlocks;
176  if (m_NumInitializedBlocks < m_pOwnerAllocator->m_NumBlocksInPage)
177  *ppNextBlock = GetBlockStartAddress(m_NumInitializedBlocks);
178  else
179  *ppNextBlock = nullptr;
180  }
181 
182  void* res = m_pNextFreeBlock;
183  dbgVerifyAddress(res);
184  // Move pointer to the next free block
185  m_pNextFreeBlock = *reinterpret_cast<void**>(m_pNextFreeBlock);
186  --m_NumFreeBlocks;
187  if (m_NumFreeBlocks != 0)
188  dbgVerifyAddress(m_pNextFreeBlock);
189  else
190  VERIFY_EXPR(m_pNextFreeBlock == nullptr);
191 
192  FillWithDebugPattern(res, AllocatedBlockMemPattern, m_pOwnerAllocator->m_BlockSize);
193  return res;
194  }
195 
196  void DeAllocate(void* p)
197  {
198  VERIFY_EXPR(m_pOwnerAllocator != nullptr);
199 
200  dbgVerifyAddress(p);
201  FillWithDebugPattern(p, DeallocatedBlockMemPattern, m_pOwnerAllocator->m_BlockSize);
202  // Add block to the beginning of the linked list
203  *reinterpret_cast<void**>(p) = m_pNextFreeBlock;
204  m_pNextFreeBlock = p;
205  ++m_NumFreeBlocks;
206  }
207 
208  bool HasSpace() const { return m_NumFreeBlocks > 0; }
209  bool HasAllocations() const { return m_NumFreeBlocks < m_NumInitializedBlocks; }
210 
211  private:
212  MemoryPage(const MemoryPage&) = delete;
213  MemoryPage& operator=(const MemoryPage) = delete;
214  MemoryPage& operator=(MemoryPage&&) = delete;
215 
216  Uint32 m_NumFreeBlocks = 0; // Num of remaining blocks
217  Uint32 m_NumInitializedBlocks = 0; // Num of initialized blocks
218  void* m_pPageStart = nullptr; // Beginning of memory pool
219  void* m_pNextFreeBlock = nullptr; // Num of next free block
220  FixedBlockMemoryAllocator* m_pOwnerAllocator = nullptr;
221  };
222 
223  std::vector<MemoryPage, STDAllocatorRawMem<MemoryPage>> m_PagePool;
224  std::unordered_set<size_t, std::hash<size_t>, std::equal_to<size_t>, STDAllocatorRawMem<size_t>> m_AvailablePages;
225 
226  using AddrToPageIdMapElem = std::pair<void* const, size_t>;
227  std::unordered_map<void*, size_t, std::hash<void*>, std::equal_to<void*>, STDAllocatorRawMem<AddrToPageIdMapElem>> m_AddrToPageId;
228 
229  std::mutex m_Mutex;
230 
231  IMemoryAllocator& m_RawMemoryAllocator;
232  const size_t m_BlockSize;
233  const Uint32 m_NumBlocksInPage;
234 };
235 
236 IMemoryAllocator& GetRawAllocator();
237 
238 template <typename ObjectType>
240 {
241 public:
242  static void SetRawAllocator(IMemoryAllocator& Allocator)
243  {
244 #ifdef DILIGENT_DEBUG
245  if (m_bPoolInitialized && m_pRawAllocator != &Allocator)
246  {
247  LOG_WARNING_MESSAGE("Setting pool raw allocator after the pool has been initialized has no effect");
248  }
249 #endif
250  m_pRawAllocator = &Allocator;
251  }
252  static void SetPageSize(Uint32 NumAllocationsInPage)
253  {
254 #ifdef DILIGENT_DEBUG
255  if (m_bPoolInitialized && m_NumAllocationsInPage != NumAllocationsInPage)
256  {
257  LOG_WARNING_MESSAGE("Setting pool page size after the pool has been initialized has no effect");
258  }
259 #endif
260  m_NumAllocationsInPage = NumAllocationsInPage;
261  }
262  static ObjectPool& GetPool()
263  {
264  static ObjectPool ThePool;
265 #ifdef DILIGENT_DEBUG
266  m_bPoolInitialized = true;
267 #endif
268  return ThePool;
269  }
270 
271  template <typename... CtorArgTypes>
272  ObjectType* NewObject(const Char* dbgDescription, const char* dbgFileName, const Int32 dbgLineNumber, CtorArgTypes&&... CtorArgs)
273  {
274  void* pRawMem = m_FixedBlockAlloctor.Allocate(sizeof(ObjectType), dbgDescription, dbgFileName, dbgLineNumber);
275  try
276  {
277  return new (pRawMem) ObjectType(std::forward<CtorArgTypes>(CtorArgs)...);
278  }
279  catch (...)
280  {
281  m_FixedBlockAlloctor.Free(pRawMem);
282  return nullptr;
283  }
284  }
285 
286  void Destroy(ObjectType* pObj)
287  {
288  if (pObj != nullptr)
289  {
290  pObj->~ObjectType();
291  m_FixedBlockAlloctor.Free(pObj);
292  }
293  }
294 
295 private:
296  static Uint32 m_NumAllocationsInPage;
297  static IMemoryAllocator* m_pRawAllocator;
298 
299  ObjectPool() :
300  m_FixedBlockAlloctor(m_pRawAllocator ? *m_pRawAllocator : GetRawAllocator(), sizeof(ObjectType), m_NumAllocationsInPage)
301  {}
302 #ifdef DILIGENT_DEBUG
303  static bool m_bPoolInitialized;
304 #endif
305  FixedBlockMemoryAllocator m_FixedBlockAlloctor;
306 };
307 template <typename ObjectType>
308 Uint32 ObjectPool<ObjectType>::m_NumAllocationsInPage = 64;
309 
310 template <typename ObjectType>
311 IMemoryAllocator* ObjectPool<ObjectType>::m_pRawAllocator = nullptr;
312 
313 #ifdef DILIGENT_DEBUG
314 template <typename ObjectType>
315 bool ObjectPool<ObjectType>::m_bPoolInitialized = false;
316 #endif
317 
318 #define SET_POOL_RAW_ALLOCATOR(ObjectType, Allocator) ObjectPool<ObjectType>::SetRawAllocator(Allocator)
319 #define SET_POOL_PAGE_SIZE(ObjectType, NumAllocationsInPage) ObjectPool<ObjectType>::SetPageSize(NumAllocationsInPage)
320 #define NEW_POOL_OBJECT(ObjectType, Desc, ...) ObjectPool<ObjectType>::GetPool().NewObject(Desc, __FILE__, __LINE__, ##__VA_ARGS__)
321 #define DESTROY_POOL_OBJECT(pObject) ObjectPool<std::remove_reference<decltype(*pObject)>::type>::GetPool().Destroy(pObject)
322 
323 } // namespace Diligent
Diligent::ObjectPool::Destroy
void Destroy(ObjectType *pObj)
Definition: FixedBlockMemoryAllocator.hpp:286
Diligent::Char
char Char
Definition: BasicTypes.h:64
STDAllocator.hpp
Diligent::ObjectPool::NewObject
ObjectType * NewObject(const Char *dbgDescription, const char *dbgFileName, const Int32 dbgLineNumber, CtorArgTypes &&... CtorArgs)
Definition: FixedBlockMemoryAllocator.hpp:272
Diligent::FixedBlockMemoryAllocator::Allocate
virtual void * Allocate(size_t Size, const Char *dbgDescription, const char *dbgFileName, const Int32 dbgLineNumber) override final
Allocates block of memory.
Definition: FixedBlockMemoryAllocator.cpp:75
Diligent::ObjectPool::SetPageSize
static void SetPageSize(Uint32 NumAllocationsInPage)
Definition: FixedBlockMemoryAllocator.hpp:252
Diligent::Int32
int32_t Int32
32-bit signed integer
Definition: BasicTypes.h:46
Diligent::FixedBlockMemoryAllocator::~FixedBlockMemoryAllocator
~FixedBlockMemoryAllocator()
Definition: FixedBlockMemoryAllocator.cpp:57
Diligent::GetRawAllocator
IMemoryAllocator & GetRawAllocator()
Returns raw memory allocator.
Definition: EngineMemory.cpp:51
dbgVerifyAddress
#define dbgVerifyAddress(...)
Definition: FixedBlockMemoryAllocator.hpp:140
Diligent::Uint32
uint32_t Uint32
32-bit unsigned integer
Definition: BasicTypes.h:51
FillWithDebugPattern
#define FillWithDebugPattern(...)
Definition: FixedBlockMemoryAllocator.hpp:52
Diligent::ObjectPool::GetPool
static ObjectPool & GetPool()
Definition: FixedBlockMemoryAllocator.hpp:262
Diligent::ObjectPool
Definition: FixedBlockMemoryAllocator.hpp:239
Diligent::IMemoryAllocator
Base interface for a raw memory allocator.
Definition: MemoryAllocator.h:41
LOG_WARNING_MESSAGE
#define LOG_WARNING_MESSAGE(...)
Definition: Errors.hpp:123
Diligent::Uint8
uint8_t Uint8
8-bit unsigned integer
Definition: BasicTypes.h:53
Diligent::FixedBlockMemoryAllocator::Free
virtual void Free(void *Ptr) override final
Releases memory.
Definition: FixedBlockMemoryAllocator.cpp:101
VERIFY_EXPR
#define VERIFY_EXPR(...)
Definition: DebugUtilities.hpp:79
VERIFY
#define VERIFY(...)
Definition: DebugUtilities.hpp:76
Diligent::FixedBlockMemoryAllocator
Memory allocator that allocates memory in a fixed-size chunks.
Definition: FixedBlockMemoryAllocator.hpp:56
Diligent::FixedBlockMemoryAllocator::FixedBlockMemoryAllocator
FixedBlockMemoryAllocator(IMemoryAllocator &RawMemoryAllocator, size_t BlockSize, Uint32 NumBlocksInPage)
Definition: FixedBlockMemoryAllocator.cpp:41
Diligent::IMemoryAllocator::Allocate
virtual void * Allocate(size_t Size, const Char *dbgDescription, const char *dbgFileName, const Int32 dbgLineNumber)=0
Allocates block of memory.
Diligent::ObjectPool::SetRawAllocator
static void SetRawAllocator(IMemoryAllocator &Allocator)
Definition: FixedBlockMemoryAllocator.hpp:242
Diligent
The library uses Direct3D-style math:
Definition: AdvancedMath.hpp:37