Diligent Engine  v.2.4.g
VariableSizeAllocationsManager.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 // Helper class that handles free memory block management to accommodate variable-size allocation requests
29 // See http://diligentgraphics.com/diligent-engine/architecture/d3d12/variable-size-memory-allocations-manager/
30 
31 #pragma once
32 
33 #include <map>
34 #include <algorithm>
35 
36 #include "../../../Primitives/interface/MemoryAllocator.h"
37 #include "../../../Platforms/Basic/interface/DebugUtilities.hpp"
38 #include "../../../Common/interface/Align.hpp"
39 #include "../../../Common/interface/STDAllocator.hpp"
40 
41 namespace Diligent
42 {
43 // The class handles free memory block management to accommodate variable-size allocation requests.
44 // It keeps track of free blocks only and does not record allocation sizes. The class uses two ordered maps
45 // to facilitate operations. The first map keeps blocks sorted by their offsets. The second multimap keeps blocks
46 // sorted by their sizes. The elements of the two maps reference each other, which enables efficient block
47 // insertion, removal and merging.
48 //
49 // 8 32 64 104
50 // |<---16--->| |<-----24------>| |<---16--->| |<-----32----->|
51 //
52 //
53 // m_FreeBlocksBySize m_FreeBlocksByOffset
54 // size->offset offset->size
55 //
56 // 16 ------------------> 8 ----------> {size = 16, &m_FreeBlocksBySize[0]}
57 //
58 // 16 ------. .-------> 32 ----------> {size = 24, &m_FreeBlocksBySize[2]}
59 // '.'
60 // 24 -------' '--------> 64 ----------> {size = 16, &m_FreeBlocksBySize[1]}
61 //
62 // 32 ------------------> 104 ----------> {size = 32, &m_FreeBlocksBySize[3]}
63 //
65 {
66 public:
67  using OffsetType = size_t;
68 
69 private:
70  struct FreeBlockInfo;
71 
72  // Type of the map that keeps memory blocks sorted by their offsets
73  using TFreeBlocksByOffsetMap =
74  std::map<OffsetType,
75  FreeBlockInfo,
76  std::less<OffsetType>, // Standard ordering
78  >;
79 
80  // Type of the map that keeps memory blocks sorted by their sizes
81  using TFreeBlocksBySizeMap =
82  std::multimap<OffsetType,
83  TFreeBlocksByOffsetMap::iterator,
84  std::less<OffsetType>, // Standard ordering
86  >;
87 
88  struct FreeBlockInfo
89  {
90  // Block size (no reserved space for the size of the allocation)
91  OffsetType Size;
92 
93  // Iterator referencing this block in the multimap sorted by the block size
94  TFreeBlocksBySizeMap::iterator OrderBySizeIt;
95 
96  FreeBlockInfo(OffsetType _Size) :
97  Size(_Size) {}
98  };
99 
100 public:
102  m_FreeBlocksByOffset(STD_ALLOCATOR_RAW_MEM(TFreeBlocksByOffsetMap::value_type, Allocator, "Allocator for map<OffsetType, FreeBlockInfo>")),
103  m_FreeBlocksBySize(STD_ALLOCATOR_RAW_MEM(TFreeBlocksBySizeMap::value_type, Allocator, "Allocator for multimap<OffsetType, TFreeBlocksByOffsetMap::iterator>")),
104  m_MaxSize(MaxSize),
105  m_FreeSize(MaxSize)
106  {
107  // Insert single maximum-size block
108  AddNewBlock(0, m_MaxSize);
109  ResetCurrAlignment();
110 
111 #ifdef DILIGENT_DEBUG
112  DbgVerifyList();
113 #endif
114  }
115 
117  {
118 #ifdef DILIGENT_DEBUG
119  if (!m_FreeBlocksByOffset.empty() || !m_FreeBlocksBySize.empty())
120  {
121  VERIFY(m_FreeBlocksByOffset.size() == 1, "Single free block is expected");
122  VERIFY(m_FreeBlocksByOffset.begin()->first == 0, "Head chunk offset is expected to be 0");
123  VERIFY(m_FreeBlocksByOffset.begin()->second.Size == m_MaxSize, "Head chunk size is expected to be ", m_MaxSize);
124  VERIFY_EXPR(m_FreeBlocksByOffset.begin()->second.OrderBySizeIt == m_FreeBlocksBySize.begin());
125  VERIFY(m_FreeBlocksBySize.size() == m_FreeBlocksByOffset.size(), "Sizes of the two maps must be equal");
126 
127  VERIFY(m_FreeBlocksBySize.size() == 1, "Single free block is expected");
128  VERIFY(m_FreeBlocksBySize.begin()->first == m_MaxSize, "Head chunk size is expected to be ", m_MaxSize);
129  VERIFY(m_FreeBlocksBySize.begin()->second == m_FreeBlocksByOffset.begin(), "Incorrect first block");
130  }
131 #endif
132  }
133 
134  // clang-format off
136  m_FreeBlocksByOffset {std::move(rhs.m_FreeBlocksByOffset)},
137  m_FreeBlocksBySize {std::move(rhs.m_FreeBlocksBySize) },
138  m_MaxSize {rhs.m_MaxSize },
139  m_FreeSize {rhs.m_FreeSize },
140  m_CurrAlignment {rhs.m_CurrAlignment}
141  {
142  // clang-format on
143  rhs.m_MaxSize = 0;
144  rhs.m_FreeSize = 0;
145  rhs.m_CurrAlignment = 0;
146  }
147 
148  // clang-format off
152  // clang-format on
153 
154  // Offset returned by Allocate() may not be aligned, but the size of the allocation
155  // is sufficient to properly align it
156  struct Allocation
157  {
158  // clang-format off
160  UnalignedOffset{offset},
161  Size {size }
162  {}
163  // clang-format on
164 
166 
167  static constexpr OffsetType InvalidOffset = ~OffsetType{0};
169  {
170  return Allocation{InvalidOffset, 0};
171  }
172 
173  bool IsValid() const
174  {
176  }
177 
178  bool operator==(const Allocation& rhs) const
179  {
180  return UnalignedOffset == rhs.UnalignedOffset &&
181  Size == rhs.Size;
182  }
183 
186  };
187 
189  {
190  VERIFY_EXPR(Size > 0);
191  VERIFY(IsPowerOfTwo(Alignment), "Alignment (", Alignment, ") must be power of 2");
192  Size = AlignUp(Size, Alignment);
193  if (m_FreeSize < Size)
195 
196  auto AlignmentReserve = (Alignment > m_CurrAlignment) ? Alignment - m_CurrAlignment : 0;
197  // Get the first block that is large enough to encompass Size + AlignmentReserve bytes
198  // lower_bound() returns an iterator pointing to the first element that
199  // is not less (i.e. >= ) than key
200  auto SmallestBlockItIt = m_FreeBlocksBySize.lower_bound(Size + AlignmentReserve);
201  if (SmallestBlockItIt == m_FreeBlocksBySize.end())
203 
204  auto SmallestBlockIt = SmallestBlockItIt->second;
205  VERIFY_EXPR(Size + AlignmentReserve <= SmallestBlockIt->second.Size);
206  VERIFY_EXPR(SmallestBlockIt->second.Size == SmallestBlockItIt->first);
207 
208  // SmallestBlockIt.Offset
209  // | |
210  // |<------SmallestBlockIt.Size------>|
211  // |<------Size------>|<---NewSize--->|
212  // | |
213  // Offset NewOffset
214  //
215  auto Offset = SmallestBlockIt->first;
216  VERIFY_EXPR(Offset % m_CurrAlignment == 0);
217  auto AlignedOffset = AlignUp(Offset, Alignment);
218  auto AdjustedSize = Size + (AlignedOffset - Offset);
219  VERIFY_EXPR(AdjustedSize <= Size + AlignmentReserve);
220  auto NewOffset = Offset + AdjustedSize;
221  auto NewSize = SmallestBlockIt->second.Size - AdjustedSize;
222  VERIFY_EXPR(SmallestBlockItIt == SmallestBlockIt->second.OrderBySizeIt);
223  m_FreeBlocksBySize.erase(SmallestBlockItIt);
224  m_FreeBlocksByOffset.erase(SmallestBlockIt);
225  if (NewSize > 0)
226  {
227  AddNewBlock(NewOffset, NewSize);
228  }
229 
230  m_FreeSize -= AdjustedSize;
231 
232  if ((Size & (m_CurrAlignment - 1)) != 0)
233  {
234  if (IsPowerOfTwo(Size))
235  {
236  VERIFY_EXPR(Size >= Alignment && Size < m_CurrAlignment);
237  m_CurrAlignment = Size;
238  }
239  else
240  {
241  m_CurrAlignment = std::min(m_CurrAlignment, Alignment);
242  }
243  }
244 
245 #ifdef DILIGENT_DEBUG
246  DbgVerifyList();
247 #endif
248  return Allocation{Offset, AdjustedSize};
249  }
250 
251  void Free(Allocation&& allocation)
252  {
253  VERIFY_EXPR(allocation.IsValid());
254  Free(allocation.UnalignedOffset, allocation.Size);
255  allocation = Allocation{};
256  }
257 
258  void Free(OffsetType Offset, OffsetType Size)
259  {
260  VERIFY_EXPR(Offset != Allocation::InvalidOffset && Offset + Size <= m_MaxSize);
261 
262  // Find the first element whose offset is greater than the specified offset.
263  // upper_bound() returns an iterator pointing to the first element in the
264  // container whose key is considered to go after k.
265  auto NextBlockIt = m_FreeBlocksByOffset.upper_bound(Offset);
266 #ifdef DILIGENT_DEBUG
267  {
268  auto LowBnd = m_FreeBlocksByOffset.lower_bound(Offset); // First element whose offset is >=
269  // Since zero-size allocations are not allowed, lower bound must always be equal to the upper bound
270  VERIFY_EXPR(LowBnd == NextBlockIt);
271  }
272 #endif
273  // Block being deallocated must not overlap with the next block
274  VERIFY_EXPR(NextBlockIt == m_FreeBlocksByOffset.end() || Offset + Size <= NextBlockIt->first);
275  auto PrevBlockIt = NextBlockIt;
276  if (PrevBlockIt != m_FreeBlocksByOffset.begin())
277  {
278  --PrevBlockIt;
279  // Block being deallocated must not overlap with the previous block
280  VERIFY_EXPR(Offset >= PrevBlockIt->first + PrevBlockIt->second.Size);
281  }
282  else
283  PrevBlockIt = m_FreeBlocksByOffset.end();
284 
285  OffsetType NewSize, NewOffset;
286  if (PrevBlockIt != m_FreeBlocksByOffset.end() && Offset == PrevBlockIt->first + PrevBlockIt->second.Size)
287  {
288  // PrevBlock.Offset Offset
289  // | |
290  // |<-----PrevBlock.Size----->|<------Size-------->|
291  //
292  NewSize = PrevBlockIt->second.Size + Size;
293  NewOffset = PrevBlockIt->first;
294 
295  if (NextBlockIt != m_FreeBlocksByOffset.end() && Offset + Size == NextBlockIt->first)
296  {
297  // PrevBlock.Offset Offset NextBlock.Offset
298  // | | |
299  // |<-----PrevBlock.Size----->|<------Size-------->|<-----NextBlock.Size----->|
300  //
301  NewSize += NextBlockIt->second.Size;
302  m_FreeBlocksBySize.erase(PrevBlockIt->second.OrderBySizeIt);
303  m_FreeBlocksBySize.erase(NextBlockIt->second.OrderBySizeIt);
304  // Delete the range of two blocks
305  ++NextBlockIt;
306  m_FreeBlocksByOffset.erase(PrevBlockIt, NextBlockIt);
307  }
308  else
309  {
310  // PrevBlock.Offset Offset NextBlock.Offset
311  // | | |
312  // |<-----PrevBlock.Size----->|<------Size-------->| ~ ~ ~ |<-----NextBlock.Size----->|
313  //
314  m_FreeBlocksBySize.erase(PrevBlockIt->second.OrderBySizeIt);
315  m_FreeBlocksByOffset.erase(PrevBlockIt);
316  }
317  }
318  else if (NextBlockIt != m_FreeBlocksByOffset.end() && Offset + Size == NextBlockIt->first)
319  {
320  // PrevBlock.Offset Offset NextBlock.Offset
321  // | | |
322  // |<-----PrevBlock.Size----->| ~ ~ ~ |<------Size-------->|<-----NextBlock.Size----->|
323  //
324  NewSize = Size + NextBlockIt->second.Size;
325  NewOffset = Offset;
326  m_FreeBlocksBySize.erase(NextBlockIt->second.OrderBySizeIt);
327  m_FreeBlocksByOffset.erase(NextBlockIt);
328  }
329  else
330  {
331  // PrevBlock.Offset Offset NextBlock.Offset
332  // | | |
333  // |<-----PrevBlock.Size----->| ~ ~ ~ |<------Size-------->| ~ ~ ~ |<-----NextBlock.Size----->|
334  //
335  NewSize = Size;
336  NewOffset = Offset;
337  }
338 
339  AddNewBlock(NewOffset, NewSize);
340 
341  m_FreeSize += Size;
342  if (IsEmpty())
343  {
344  // Reset current alignment
346  ResetCurrAlignment();
347  }
348 
349 #ifdef DILIGENT_DEBUG
350  DbgVerifyList();
351 #endif
352  }
353 
354  // clang-format off
355  bool IsFull() const{ return m_FreeSize==0; };
356  bool IsEmpty()const{ return m_FreeSize==m_MaxSize; };
357  OffsetType GetMaxSize() const{return m_MaxSize;}
358  OffsetType GetFreeSize()const{return m_FreeSize;}
359  OffsetType GetUsedSize()const{return m_MaxSize - m_FreeSize;}
360  // clang-format on
361 
362  size_t GetNumFreeBlocks() const
363  {
364  return m_FreeBlocksByOffset.size();
365  }
366 
367  void Extend(size_t ExtraSize)
368  {
369  size_t NewBlockOffset = m_MaxSize;
370  size_t NewBlockSize = ExtraSize;
371 
372  if (!m_FreeBlocksByOffset.empty())
373  {
374  auto LastBlockIt = m_FreeBlocksByOffset.end();
375  --LastBlockIt;
376 
377  const auto LastBlockOffset = LastBlockIt->first;
378  const auto LastBlockSize = LastBlockIt->second.Size;
379  if (LastBlockOffset + LastBlockSize == m_MaxSize)
380  {
381  // Extend the last block
382  NewBlockOffset = LastBlockOffset;
383  NewBlockSize += LastBlockSize;
384 
385  VERIFY_EXPR(LastBlockIt->second.OrderBySizeIt->first == LastBlockSize &&
386  LastBlockIt->second.OrderBySizeIt->second == LastBlockIt);
387  m_FreeBlocksBySize.erase(LastBlockIt->second.OrderBySizeIt);
388  m_FreeBlocksByOffset.erase(LastBlockIt);
389  }
390  }
391 
392  AddNewBlock(NewBlockOffset, NewBlockSize);
393 
394  m_MaxSize += ExtraSize;
395  m_FreeSize += ExtraSize;
396 
397 #ifdef DILIGENT_DEBUG
398  DbgVerifyList();
399 #endif
400  }
401 
402 private:
403  void AddNewBlock(OffsetType Offset, OffsetType Size)
404  {
405  auto NewBlockIt = m_FreeBlocksByOffset.emplace(Offset, Size);
406  VERIFY_EXPR(NewBlockIt.second);
407  auto OrderIt = m_FreeBlocksBySize.emplace(Size, NewBlockIt.first);
408  NewBlockIt.first->second.OrderBySizeIt = OrderIt;
409  }
410 
411  void ResetCurrAlignment()
412  {
413  for (m_CurrAlignment = 1; m_CurrAlignment * 2 <= m_MaxSize; m_CurrAlignment *= 2)
414  {}
415  }
416 
417 #ifdef DILIGENT_DEBUG
418  void DbgVerifyList()
419  {
420  OffsetType TotalFreeSize = 0;
421 
422  VERIFY_EXPR(IsPowerOfTwo(m_CurrAlignment));
423  auto BlockIt = m_FreeBlocksByOffset.begin();
424  auto PrevBlockIt = m_FreeBlocksByOffset.end();
425  VERIFY_EXPR(m_FreeBlocksByOffset.size() == m_FreeBlocksBySize.size());
426  while (BlockIt != m_FreeBlocksByOffset.end())
427  {
428  VERIFY_EXPR(BlockIt->first >= 0 && BlockIt->first + BlockIt->second.Size <= m_MaxSize);
429  VERIFY((BlockIt->first & (m_CurrAlignment - 1)) == 0, "Block offset (", BlockIt->first, ") is not ", m_CurrAlignment, "-aligned");
430  if (BlockIt->first + BlockIt->second.Size < m_MaxSize)
431  VERIFY((BlockIt->second.Size & (m_CurrAlignment - 1)) == 0, "All block sizes except for the last one must be ", m_CurrAlignment, "-aligned");
432  VERIFY_EXPR(BlockIt == BlockIt->second.OrderBySizeIt->second);
433  VERIFY_EXPR(BlockIt->second.Size == BlockIt->second.OrderBySizeIt->first);
434  // PrevBlock.Offset BlockIt.first
435  // | |
436  // ~ ~ |<-----PrevBlock.Size----->| ~ ~ ~ |<------Size-------->| ~ ~ ~
437  //
438  VERIFY(PrevBlockIt == m_FreeBlocksByOffset.end() || BlockIt->first > PrevBlockIt->first + PrevBlockIt->second.Size, "Unmerged adjacent or overlapping blocks detected");
439  TotalFreeSize += BlockIt->second.Size;
440 
441  PrevBlockIt = BlockIt;
442  ++BlockIt;
443  }
444 
445  auto OrderIt = m_FreeBlocksBySize.begin();
446  while (OrderIt != m_FreeBlocksBySize.end())
447  {
448  VERIFY_EXPR(OrderIt->first == OrderIt->second->second.Size);
449  ++OrderIt;
450  }
451 
452  VERIFY_EXPR(TotalFreeSize == m_FreeSize);
453  }
454 #endif
455 
456  TFreeBlocksByOffsetMap m_FreeBlocksByOffset;
457  TFreeBlocksBySizeMap m_FreeBlocksBySize;
458 
459  OffsetType m_MaxSize = 0;
460  OffsetType m_FreeSize = 0;
461  OffsetType m_CurrAlignment = 0;
462  // When adding new members, do not forget to update move ctor
463 };
464 } // namespace Diligent
Diligent::VariableSizeAllocationsManager::Allocation::Allocation
Allocation()
Definition: VariableSizeAllocationsManager.hpp:165
Diligent::VariableSizeAllocationsManager::IsEmpty
bool IsEmpty() const
Definition: VariableSizeAllocationsManager.hpp:356
Diligent::VariableSizeAllocationsManager::Extend
void Extend(size_t ExtraSize)
Definition: VariableSizeAllocationsManager.hpp:367
Diligent::VariableSizeAllocationsManager::IsFull
bool IsFull() const
Definition: VariableSizeAllocationsManager.hpp:355
Diligent::VariableSizeAllocationsManager
Definition: VariableSizeAllocationsManager.hpp:64
Diligent::VariableSizeAllocationsManager::GetMaxSize
OffsetType GetMaxSize() const
Definition: VariableSizeAllocationsManager.hpp:357
Diligent::VariableSizeAllocationsManager::operator=
VariableSizeAllocationsManager & operator=(VariableSizeAllocationsManager &&rhs)=default
Diligent::VariableSizeAllocationsManager::Allocation
Definition: VariableSizeAllocationsManager.hpp:156
Diligent::VariableSizeAllocationsManager::GetUsedSize
OffsetType GetUsedSize() const
Definition: VariableSizeAllocationsManager.hpp:359
Diligent::VariableSizeAllocationsManager::VariableSizeAllocationsManager
VariableSizeAllocationsManager(OffsetType MaxSize, IMemoryAllocator &Allocator)
Definition: VariableSizeAllocationsManager.hpp:101
std::min
Diligent::Vector2< T > min(const Diligent::Vector2< T > &Left, const Diligent::Vector2< T > &Right)
Definition: BasicMath.hpp:2289
Diligent::STDAllocator
Definition: STDAllocator.hpp:53
Diligent::VariableSizeAllocationsManager::Allocate
Allocation Allocate(OffsetType Size, OffsetType Alignment)
Definition: VariableSizeAllocationsManager.hpp:188
Diligent::VariableSizeAllocationsManager::OffsetType
size_t OffsetType
Definition: VariableSizeAllocationsManager.hpp:67
Diligent::VariableSizeAllocationsManager::Allocation::InvalidOffset
static constexpr OffsetType InvalidOffset
Definition: VariableSizeAllocationsManager.hpp:167
Diligent::VariableSizeAllocationsManager::VariableSizeAllocationsManager
VariableSizeAllocationsManager(VariableSizeAllocationsManager &&rhs) noexcept
Definition: VariableSizeAllocationsManager.hpp:135
Diligent::VariableSizeAllocationsManager::GetNumFreeBlocks
size_t GetNumFreeBlocks() const
Definition: VariableSizeAllocationsManager.hpp:362
Diligent::IsPowerOfTwo
bool IsPowerOfTwo(T val)
Definition: Align.hpp:41
Diligent::IMemoryAllocator
Base interface for a raw memory allocator.
Definition: MemoryAllocator.h:41
Diligent::VariableSizeAllocationsManager::Allocation::Allocation
Allocation(OffsetType offset, OffsetType size)
Definition: VariableSizeAllocationsManager.hpp:159
Diligent::VariableSizeAllocationsManager::Allocation::operator==
bool operator==(const Allocation &rhs) const
Definition: VariableSizeAllocationsManager.hpp:178
STD_ALLOCATOR_RAW_MEM
#define STD_ALLOCATOR_RAW_MEM(Type, Allocator, Description)
Definition: STDAllocator.hpp:179
VERIFY_EXPR
#define VERIFY_EXPR(...)
Definition: DebugUtilities.hpp:79
Diligent::VariableSizeAllocationsManager::Free
void Free(OffsetType Offset, OffsetType Size)
Definition: VariableSizeAllocationsManager.hpp:258
VERIFY
#define VERIFY(...)
Definition: DebugUtilities.hpp:76
Diligent::AlignUp
T2 ::type AlignUp(T1 val, T2 alignment)
Definition: Align.hpp:47
Diligent::VariableSizeAllocationsManager::Allocation::IsValid
bool IsValid() const
Definition: VariableSizeAllocationsManager.hpp:173
Diligent::VariableSizeAllocationsManager::Allocation::InvalidAllocation
static Allocation InvalidAllocation()
Definition: VariableSizeAllocationsManager.hpp:168
Diligent::VariableSizeAllocationsManager::~VariableSizeAllocationsManager
~VariableSizeAllocationsManager()
Definition: VariableSizeAllocationsManager.hpp:116
Diligent::VariableSizeAllocationsManager::Allocation::UnalignedOffset
OffsetType UnalignedOffset
Definition: VariableSizeAllocationsManager.hpp:184
Diligent
The library uses Direct3D-style math:
Definition: AdvancedMath.hpp:37
Diligent::VariableSizeAllocationsManager::GetFreeSize
OffsetType GetFreeSize() const
Definition: VariableSizeAllocationsManager.hpp:358
Diligent::VariableSizeAllocationsManager::Allocation::Size
OffsetType Size
Definition: VariableSizeAllocationsManager.hpp:185
Diligent::VariableSizeAllocationsManager::Free
void Free(Allocation &&allocation)
Definition: VariableSizeAllocationsManager.hpp:251