Diligent Engine API Reference
VariableSizeAllocationsManager.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 // Helper class that handles free memory block management to accommodate variable-size allocation requests
25 // See http://diligentgraphics.com/diligent-engine/architecture/d3d12/variable-size-memory-allocations-manager/
26 
27 #pragma once
28 
29 #include <map>
30 #include "MemoryAllocator.h"
31 #include "STDAllocator.h"
32 #include "DebugUtilities.h"
33 
34 namespace Diligent
35 {
36  // The class handles free memory block management to accommodate variable-size allocation requests.
37  // It keeps track of free blocks only and does not record allocation sizes. The class uses two ordered maps
38  // to facilitate operations. The first map keeps blocks sorted by their offsets. The second multimap keeps blocks
39  // sorted by their sizes. The elements of the two maps reference each other, which enables efficient block
40  // insertion, removal and merging.
41  //
42  // 8 32 64 104
43  // |<---16--->| |<-----24------>| |<---16--->| |<-----32----->|
44  //
45  //
46  // m_FreeBlocksBySize m_FreeBlocksByOffset
47  // size->offset offset->size
48  //
49  // 16 ------------------> 8 ----------> {size = 16, &m_FreeBlocksBySize[0]}
50  //
51  // 16 ------. .-------> 32 ----------> {size = 24, &m_FreeBlocksBySize[2]}
52  // '.'
53  // 24 -------' '--------> 64 ----------> {size = 16, &m_FreeBlocksBySize[1]}
54  //
55  // 32 ------------------> 104 ----------> {size = 32, &m_FreeBlocksBySize[3]}
56  //
57  class VariableSizeAllocationsManager
58  {
59  public:
60  typedef size_t OffsetType;
61  static constexpr OffsetType InvalidOffset = static_cast<OffsetType>(-1);
62 
63  private:
64  struct FreeBlockInfo;
65 
66  // Type of the map that keeps memory blocks sorted by their offsets
67  using TFreeBlocksByOffsetMap =
68  std::map<OffsetType,
69  FreeBlockInfo,
70  std::less<OffsetType>, // Standard ordering
71  STDAllocatorRawMem<std::pair<const OffsetType, FreeBlockInfo>> // Raw memory allocator
72  >;
73 
74  // Type of the map that keeps memory blocks sorted by their sizes
75  using TFreeBlocksBySizeMap =
76  std::multimap<OffsetType,
77  TFreeBlocksByOffsetMap::iterator,
78  std::less<OffsetType>, // Standard ordering
79  STDAllocatorRawMem<std::pair<const OffsetType, TFreeBlocksByOffsetMap::iterator>> // Raw memory allocator
80  >;
81 
82  struct FreeBlockInfo
83  {
84  // Block size (no reserved space for the size of the allocation)
85  OffsetType Size;
86 
87  // Iterator referencing this block in the multimap sorted by the block size
88  TFreeBlocksBySizeMap::iterator OrderBySizeIt;
89 
90  FreeBlockInfo(OffsetType _Size) : Size(_Size){}
91  };
92 
93  public:
94  VariableSizeAllocationsManager(OffsetType MaxSize, IMemoryAllocator &Allocator) :
95  m_MaxSize(MaxSize),
96  m_FreeSize(MaxSize),
97  m_FreeBlocksByOffset( STD_ALLOCATOR_RAW_MEM(TFreeBlocksByOffsetMap::value_type, Allocator, "Allocator for map<OffsetType, FreeBlockInfo>") ),
98  m_FreeBlocksBySize( STD_ALLOCATOR_RAW_MEM(TFreeBlocksBySizeMap::value_type, Allocator, "Allocator for multimap<OffsetType, TFreeBlocksByOffsetMap::iterator>") )
99  {
100  // Insert single maximum-size block
101  AddNewBlock(0, m_MaxSize);
102 
103 #ifdef _DEBUG
104  DbgVerifyList();
105 #endif
106  }
107 
108  ~VariableSizeAllocationsManager()
109  {
110 #ifdef _DEBUG
111  if( !m_FreeBlocksByOffset.empty() || !m_FreeBlocksBySize.empty() )
112  {
113  VERIFY(m_FreeBlocksByOffset.size() == 1, "Single free block is expected");
114  VERIFY(m_FreeBlocksByOffset.begin()->first == 0, "Head chunk offset is expected to be 0");
115  VERIFY(m_FreeBlocksByOffset.begin()->second.Size == m_MaxSize, "Head chunk size is expected to be ", m_MaxSize);
116  VERIFY_EXPR(m_FreeBlocksByOffset.begin()->second.OrderBySizeIt == m_FreeBlocksBySize.begin());
117  VERIFY(m_FreeBlocksBySize.size() == m_FreeBlocksByOffset.size(), "Sizes of the two maps must be equal");
118 
119  VERIFY(m_FreeBlocksBySize.size() == 1, "Single free block is expected");
120  VERIFY(m_FreeBlocksBySize.begin()->first == m_MaxSize, "Head chunk size is expected to be ", m_MaxSize);
121  VERIFY(m_FreeBlocksBySize.begin()->second == m_FreeBlocksByOffset.begin(), "Incorrect first block");
122  }
123 #endif
124  }
125 
126  VariableSizeAllocationsManager(VariableSizeAllocationsManager&& rhs) :
127  m_FreeBlocksByOffset(std::move(rhs.m_FreeBlocksByOffset)),
128  m_FreeBlocksBySize(std::move(rhs.m_FreeBlocksBySize)),
129  m_MaxSize(rhs.m_MaxSize),
130  m_FreeSize(rhs.m_FreeSize)
131  {
132  //rhs.m_MaxSize = 0; - const
133  rhs.m_FreeSize = 0;
134  }
135 
136  VariableSizeAllocationsManager& operator = (VariableSizeAllocationsManager&& rhs) = default;
137  VariableSizeAllocationsManager(const VariableSizeAllocationsManager&) = delete;
138  VariableSizeAllocationsManager& operator = (const VariableSizeAllocationsManager&) = delete;
139 
140  OffsetType Allocate(OffsetType Size)
141  {
142  VERIFY_EXPR(Size != 0);
143  if(m_FreeSize < Size)
144  return InvalidOffset;
145 
146  // Get the first block that is large enough to encompass Size bytes
147  // lower_bound() returns an iterator pointing to the first element that
148  // is not less (i.e. >= ) than key
149  auto SmallestBlockItIt = m_FreeBlocksBySize.lower_bound(Size);
150  if(SmallestBlockItIt == m_FreeBlocksBySize.end())
151  return InvalidOffset;
152 
153  auto SmallestBlockIt = SmallestBlockItIt->second;
154  VERIFY_EXPR(Size <= SmallestBlockIt->second.Size);
155  VERIFY_EXPR(SmallestBlockIt->second.Size == SmallestBlockItIt->first);
156 
157  // SmallestBlockIt.Offset
158  // | |
159  // |<------SmallestBlockIt.Size------>|
160  // |<------Size------>|<---NewSize--->|
161  // | |
162  // Offset NewOffset
163  //
164  auto Offset = SmallestBlockIt->first;
165  auto NewOffset = Offset + Size;
166  auto NewSize = SmallestBlockIt->second.Size - Size;
167  VERIFY_EXPR(SmallestBlockItIt == SmallestBlockIt->second.OrderBySizeIt);
168  m_FreeBlocksBySize.erase(SmallestBlockItIt);
169  m_FreeBlocksByOffset.erase(SmallestBlockIt);
170  if (NewSize > 0)
171  {
172  AddNewBlock(NewOffset, NewSize);
173  }
174 
175  m_FreeSize -= Size;
176 
177 #ifdef _DEBUG
178  DbgVerifyList();
179 #endif
180  return Offset;
181  }
182 
183  void Free(OffsetType Offset, OffsetType Size)
184  {
185  VERIFY_EXPR(Offset+Size <= m_MaxSize);
186 
187  // Find the first element whose offset is greater than the specified offset.
188  // upper_bound() returns an iterator pointing to the first element in the
189  // container whose key is considered to go after k.
190  auto NextBlockIt = m_FreeBlocksByOffset.upper_bound(Offset);
191 #ifdef _DEBUG
192  {
193  auto LowBnd = m_FreeBlocksByOffset.lower_bound(Offset); // First element whose offset is >=
194  // Since zero-size allocations are not allowed, lower bound must always be equal to the upper bound
195  VERIFY_EXPR(LowBnd == NextBlockIt);
196  }
197 #endif
198  // Block being deallocated must not overlap with the next block
199  VERIFY_EXPR(NextBlockIt == m_FreeBlocksByOffset.end() || Offset+Size <= NextBlockIt->first);
200  auto PrevBlockIt = NextBlockIt;
201  if(PrevBlockIt != m_FreeBlocksByOffset.begin())
202  {
203  --PrevBlockIt;
204  // Block being deallocated must not overlap with the previous block
205  VERIFY_EXPR(Offset >= PrevBlockIt->first + PrevBlockIt->second.Size);
206  }
207  else
208  PrevBlockIt = m_FreeBlocksByOffset.end();
209 
210  OffsetType NewSize, NewOffset;
211  if(PrevBlockIt != m_FreeBlocksByOffset.end() && Offset == PrevBlockIt->first + PrevBlockIt->second.Size)
212  {
213  // PrevBlock.Offset Offset
214  // | |
215  // |<-----PrevBlock.Size----->|<------Size-------->|
216  //
217  NewSize = PrevBlockIt->second.Size + Size;
218  NewOffset = PrevBlockIt->first;
219 
220  if (NextBlockIt != m_FreeBlocksByOffset.end() && Offset + Size == NextBlockIt->first)
221  {
222  // PrevBlock.Offset Offset NextBlock.Offset
223  // | | |
224  // |<-----PrevBlock.Size----->|<------Size-------->|<-----NextBlock.Size----->|
225  //
226  NewSize += NextBlockIt->second.Size;
227  m_FreeBlocksBySize.erase(PrevBlockIt->second.OrderBySizeIt);
228  m_FreeBlocksBySize.erase(NextBlockIt->second.OrderBySizeIt);
229  // Delete the range of two blocks
230  ++NextBlockIt;
231  m_FreeBlocksByOffset.erase(PrevBlockIt, NextBlockIt);
232  }
233  else
234  {
235  // PrevBlock.Offset Offset NextBlock.Offset
236  // | | |
237  // |<-----PrevBlock.Size----->|<------Size-------->| ~ ~ ~ |<-----NextBlock.Size----->|
238  //
239  m_FreeBlocksBySize.erase(PrevBlockIt->second.OrderBySizeIt);
240  m_FreeBlocksByOffset.erase(PrevBlockIt);
241  }
242  }
243  else if (NextBlockIt != m_FreeBlocksByOffset.end() && Offset + Size == NextBlockIt->first)
244  {
245  // PrevBlock.Offset Offset NextBlock.Offset
246  // | | |
247  // |<-----PrevBlock.Size----->| ~ ~ ~ |<------Size-------->|<-----NextBlock.Size----->|
248  //
249  NewSize = Size + NextBlockIt->second.Size;
250  NewOffset = Offset;
251  m_FreeBlocksBySize.erase(NextBlockIt->second.OrderBySizeIt);
252  m_FreeBlocksByOffset.erase(NextBlockIt);
253  }
254  else
255  {
256  // PrevBlock.Offset Offset NextBlock.Offset
257  // | | |
258  // |<-----PrevBlock.Size----->| ~ ~ ~ |<------Size-------->| ~ ~ ~ |<-----NextBlock.Size----->|
259  //
260  NewSize = Size;
261  NewOffset = Offset;
262  }
263 
264  AddNewBlock(NewOffset, NewSize);
265 
266  m_FreeSize += Size;
267 #ifdef _DEBUG
268  DbgVerifyList();
269 #endif
270  }
271 
272  OffsetType GetMaxSize()const{return m_MaxSize;}
273  bool IsFull()const{ return m_FreeSize==0; };
274  bool IsEmpty()const{ return m_FreeSize==m_MaxSize; };
275  OffsetType GetFreeSize()const{return m_FreeSize;}
276 
277 #ifdef _DEBUG
278  size_t DbgGetNumFreeBlocks()const{return m_FreeBlocksByOffset.size();}
279 #endif
280 
281  private:
282  void AddNewBlock(OffsetType Offset, OffsetType Size)
283  {
284  auto NewBlockIt = m_FreeBlocksByOffset.emplace(Offset, Size);
285  VERIFY_EXPR(NewBlockIt.second);
286  auto OrderIt = m_FreeBlocksBySize.emplace(Size, NewBlockIt.first);
287  NewBlockIt.first->second.OrderBySizeIt = OrderIt;
288  }
289 
290 
291 #ifdef _DEBUG
292  void DbgVerifyList()
293  {
294  OffsetType TotalFreeSize = 0;
295 
296  auto BlockIt = m_FreeBlocksByOffset.begin();
297  auto PrevBlockIt = m_FreeBlocksByOffset.end();
298  VERIFY_EXPR(m_FreeBlocksByOffset.size() == m_FreeBlocksBySize.size());
299  while (BlockIt != m_FreeBlocksByOffset.end())
300  {
301  VERIFY_EXPR(BlockIt->first >= 0 && BlockIt->first + BlockIt->second.Size <= m_MaxSize);
302  VERIFY_EXPR(BlockIt == BlockIt->second.OrderBySizeIt->second);
303  VERIFY_EXPR(BlockIt->second.Size == BlockIt->second.OrderBySizeIt->first);
304  // PrevBlock.Offset BlockIt.first
305  // | |
306  // ~ ~ |<-----PrevBlock.Size----->| ~ ~ ~ |<------Size-------->| ~ ~ ~
307  //
308  VERIFY(PrevBlockIt == m_FreeBlocksByOffset.end() || BlockIt->first > PrevBlockIt->first + PrevBlockIt->second.Size, "Unmerged adjacent or overlapping blocks detected" );
309  TotalFreeSize += BlockIt->second.Size;
310 
311  PrevBlockIt = BlockIt;
312  ++BlockIt;
313  }
314 
315  auto OrderIt = m_FreeBlocksBySize.begin();
316  while (OrderIt != m_FreeBlocksBySize.end())
317  {
318  VERIFY_EXPR(OrderIt->first == OrderIt->second->second.Size);
319  ++OrderIt;
320  }
321 
322  VERIFY_EXPR(TotalFreeSize == m_FreeSize);
323  }
324 #endif
325 
326  TFreeBlocksByOffsetMap m_FreeBlocksByOffset;
327  TFreeBlocksBySizeMap m_FreeBlocksBySize;
328 
329  const OffsetType m_MaxSize = 0;
330  OffsetType m_FreeSize = 0;
331  };
332 }
Graphics engine namespace.
Definition: AdaptiveFixedBlockAllocator.h:30
Definition: AdvancedMath.h:316