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