Diligent Engine API Reference: D:/GitHub/DiligentEngine/DiligentCore/Graphics/GraphicsAccessories/interface/VariableSizeAllocationsManager.h Source File

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 }
Diligent
Graphics engine namespace.
Definition: AdaptiveFixedBlockAllocator.h:30
std
Definition: AdvancedMath.h:316