UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
RangeAllocator.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3/*=============================================================================
4 RangeAllocator.h: Memory allocator that sub-allocates from ranges of memory
5=============================================================================*/
6
7#pragma once
8
9#include "Containers/Array.h"
10#include "HAL/CriticalSection.h"
11#include "Misc/ScopeLock.h"
12
13#define RANGE_ALLOCATOR_RECORD_STATS !UE_BUILD_SHIPPING
14
15/*
16 * This allocator has a list of chunks (fixed size committed memory regions) and each
17 * chunk keeps a list of free ranges. Allocations are served by finding a large enough
18 * free range and sub-allocating from it. Freed allocations become free ranges and are
19 * combined with adjacent free ranges if possible. It is meant to be a faster version
20 * of TGenericGrowableAllocator but imposes some restrictions on size and alignment.
21 * The restrictions are that alignments must be power of two, ChunkSize must be a multiple
22 * of MinAlignment, and the quotient of dividing ChunkSize by MinAlignment must be
23 * representable by a 16-bit unsigned integer. Note that allocations smaller than
24 * MinAlignment will be padded to MinAlignment and it cannot allocate larger than
25 * ChunkSize. It is advised to use multiple instances of range allocators where each
26 * handles a range of allocation sizes (e.g. three allocators for allocations smaller
27 * than 1 KB, between 1 KB and 128 KB, and between 128 KB and 1 MB, respectively). Doing
28 * so usually lead to better performance and less fragmentation.
29 *
30 * To use this allocator, implement your own FAllocatorModifier to customize behaviors
31 * and handle platform specific stuff. Your implementation should at minimum contain the
32 * members listed below.
33 *
34 * class FAllocatorModifier
35 * {
36 * static constexpr uint32 ChunkSize = <...>;
37 * static constexpr uint32 MinAlignment = <...>;
38 *
39 * // The type of "pointer" returned by this allocator
40 * typedef <...> FPointerType;
41 * typedef <...> FConstPointerType;
42 *
43 * // Contains information needed to represent a chunk
44 * struct FChunkModifier
45 * {
46 * // You may allocate the actual backing memory here and store its base address
47 * FChunkModifier(const FAllocatorModifier& Allocator);
48 *
49 * // Custom cleanup such as releasing backing memory
50 * ~FChunkModifier();
51 *
52 * // Reinterpret the offset in chunk such as adding it to a base address
53 * SIZE_T AdjustAllocation(SIZE_T OffsetInChunk) [const];
54 *
55 * // Whether this chunk is valid
56 * bool IsValid() const;
57 *
58 * // Whether Addr is in this chunk
59 * bool Contains(FConstPointerType Addr) const;
60 * };
61 *
62 * // Contains information needed to represent an allocation
63 * struct FAllocInfoModifier
64 * {
65 * // Initialize custom members. Ptr is the output of AdjustAllocation
66 * void InitCustomInfo(const FChunkModifier& Chunk, SIZE_T Ptr);
67 * };
68 *
69 * // Convert AdjustAllocation output to the pointer type such as a simple type cast
70 * static FPointerType MakePointer(SIZE_T Ptr);
71 *
72 * // Custom assert implementation
73 * static void Check(bool bOk);
74 * };
75 *
76 * Once the modifier is implemented, feed it to TRangeAllocator as a template argument.
77 * You may derive from TRangeAllocator<FAllocatorModifier> to do further customization
78 * like adding methods that access protected TRangeAllocator member variables.
79 */
80 template <typename FAllocatorModifier>
81class TRangeAllocator : public FAllocatorModifier
82{
83protected:
85 using FPointerType = typename FAllocatorModifier::FPointerType;
86 using FConstPointerType = typename FAllocatorModifier::FConstPointerType;
87 using FChunkModifier = typename FAllocatorModifier::FChunkModifier;
88 using FAllocInfoModifier = typename FAllocatorModifier::FAllocInfoModifier;
89
90 using FAllocatorModifier::Check;
91 using FAllocatorModifier::MakePointer;
92
93 class FChunk : public FChunkModifier
94 {
95 using FChunkModifier::AdjustAllocation;
96
97 struct FRange
98 {
99 uint16 OffsetMultiplier;
100 uint16 SizeMultiplier;
101
103 : OffsetMultiplier(InOffsetMultiplier)
104 , SizeMultiplier(InSizeMultiplier)
105 {}
106 };
107
108 TArray<FRange> FreeRanges;
109
110 public:
111 union
112 {
115 };
116
117 using FChunkModifier::IsValid;
118 using FChunkModifier::Contains;
119
123 {
124 FreeRanges.Add(FRange(0, Allocator.ChunkSize / MinAlignment));
125 }
126
128 {
129 bool bMaxRangeSizeDirty = false;
131 Check(InSize / MinAlignment <= UINT16_MAX);
132 const uint16 Size = uint16(InSize / MinAlignment);
133 Check(InAlignment >= MinAlignment && InAlignment / MinAlignment <= UINT16_MAX);
134 const uint16 Alignment = uint16(InAlignment / MinAlignment);
135 SIZE_T OffsetInChunk = 0;
136
137 // Find a large enough free range and sub-allocate from it
138 Check(FreeRanges.Num() > 0);
139 int32 Index = 0;
140 for (; Index < FreeRanges.Num(); ++Index)
141 {
142 FRange& Range = FreeRanges[Index];
143 const uint16 Start = Range.OffsetMultiplier;
144 const uint16 AlignedStart = Align(Start, Alignment);
145 const uint16 Padding = AlignedStart - Start;
148 if (Range.SizeMultiplier >= RequiredSize)
149 {
150 const uint16 RemainingSize = Range.SizeMultiplier - RequiredSize;
151 bMaxRangeSizeDirty = Range.SizeMultiplier == InOutMaxFreeRangeSize;
152 OffsetInChunk = (SIZE_T)AlignedStart * MinAlignment;
153 OutOffset = Range.OffsetMultiplier;
155 if (!RemainingSize)
156 {
158 }
159 else
160 {
161 Range.OffsetMultiplier += RequiredSize;
162 Range.SizeMultiplier = RemainingSize;
163 NewMaxFreeRangeSize = FMath::Max(NewMaxFreeRangeSize, Range.SizeMultiplier);
164 }
165
167 {
168 return AdjustAllocation(OffsetInChunk);
169 }
170 ++Index;
171 break;
172 }
173 else
174 {
175 NewMaxFreeRangeSize = FMath::Max(NewMaxFreeRangeSize, Range.SizeMultiplier);
176 }
177 }
178
179 // If MaxFreeRangeSize is dirty, loop through all the free ranges to figure out the new max
181 {
182 for (; Index < FreeRanges.Num(); ++Index)
183 {
184 const FRange& Range = FreeRanges[Index];
185 NewMaxFreeRangeSize = FMath::Max(NewMaxFreeRangeSize, Range.SizeMultiplier);
186 }
188 return AdjustAllocation(OffsetInChunk);
189 }
190
191 // No free range is large enough, return nullptr
192 return 0;
193 }
194
196 {
197 const int32 UpperBoundIndex = Algo::UpperBoundBy(FreeRanges, Offset, [](const FRange& Range) { return Range.OffsetMultiplier; });
198 Check(UpperBoundIndex >= 0 && UpperBoundIndex <= FreeRanges.Num());
199 Check(UpperBoundIndex == FreeRanges.Num() || Offset < FreeRanges[UpperBoundIndex].OffsetMultiplier);
201 Check(LowerBoundIndex >= -1 && LowerBoundIndex < FreeRanges.Num());
202 Check(LowerBoundIndex == -1 || FreeRanges[LowerBoundIndex].OffsetMultiplier < Offset);
203 bool bInsertRange = true;
204 // Try merge with left
205 if (LowerBoundIndex >= 0 && Offset == FreeRanges[LowerBoundIndex].OffsetMultiplier + FreeRanges[LowerBoundIndex].SizeMultiplier)
206 {
207 Check((uint32)Size + FreeRanges[LowerBoundIndex].SizeMultiplier <= UINT16_MAX);
208 FreeRanges[LowerBoundIndex].SizeMultiplier += Size;
209 Offset = FreeRanges[LowerBoundIndex].OffsetMultiplier;
210 Size = FreeRanges[LowerBoundIndex].SizeMultiplier;
211 bInsertRange = false;
212 }
213 // Try merge with right
214 if (UpperBoundIndex < FreeRanges.Num() && Offset + Size == FreeRanges[UpperBoundIndex].OffsetMultiplier)
215 {
216 if (bInsertRange)
217 {
218 FreeRanges[UpperBoundIndex].OffsetMultiplier = Offset;
219 Check((uint32)Size + FreeRanges[UpperBoundIndex].SizeMultiplier <= UINT16_MAX);
220 FreeRanges[UpperBoundIndex].SizeMultiplier += Size;
221 Size = FreeRanges[UpperBoundIndex].SizeMultiplier;
222 bInsertRange = false;
223 }
224 else
225 {
226 FreeRanges[LowerBoundIndex].SizeMultiplier += FreeRanges[UpperBoundIndex].SizeMultiplier;
227 Size = FreeRanges[LowerBoundIndex].SizeMultiplier;
229 }
230 }
231 // No merging, add a new free range
232 if (bInsertRange)
233 {
234 FreeRanges.Insert(FRange(Offset, Size), UpperBoundIndex);
235 Check(UpperBoundIndex + 1 == FreeRanges.Num() || Offset < FreeRanges[UpperBoundIndex + 1].OffsetMultiplier);
236 Check(UpperBoundIndex == 0 || FreeRanges[UpperBoundIndex - 1].OffsetMultiplier < Offset);
237 }
238 // Keep MaxFreeRangeSize up-to-date
240 }
241 };
242
253
255 {
256 if (InfoIndex != FreeChunkInfos.Num() - 1)
257 {
258 Chunks[FreeChunkInfos.Last().ChunkIndex].InfoIndex = InfoIndex;
259 }
261 }
262
263 void FreeChunk(uint32 ChunkIndex)
264 {
265#if RANGE_ALLOCATOR_RECORD_STATS
266 SizeTotal -= ChunkSize;
267#endif
268 const int32 InfoIndex = Chunks[ChunkIndex].InfoIndex;
269 if (InfoIndex != INDEX_NONE)
270 {
271 RemoveFreeChunkInfo(InfoIndex);
272 }
273 Chunks[ChunkIndex].~FChunk();
274 Chunks[ChunkIndex].NextFreeChunkSlot = FreeChunkSlotHead;
275 FreeChunkSlotHead = ChunkIndex;
276 }
277
278#if RANGE_ALLOCATOR_RECORD_STATS
279 SIZE_T SizeTotal = 0; // Total amount allocated in bytes including slack
280 SIZE_T SizeUsed = 0; // Size of active allocations in bytes
281#endif
282
288
289public:
290 using FAllocatorModifier::ChunkSize;
291 using FAllocatorModifier::MinAlignment;
292
299
300 template <typename... ArgTypes>
302 : FAllocatorModifier(Forward<ArgTypes>(Args)...)
303 , MinAllocSize(uint16(InMinAllocSize / MinAlignment))
305 {}
306
308 {
310
311 Alignment = Align(FMath::Max(Alignment, MinAlignment), MinAlignment);
312 Check(FMath::IsPowerOfTwo(Alignment));
313 Size = Align(Size, Alignment);
314 Check(Size <= ChunkSize);
315 Check(Size >= MinAllocSize * MinAlignment);
316
319
320 // Find a sure fit first
321 for (uint16 Index = 0; Index < NumFreeChunks; ++Index)
322 {
324 if (Size + Alignment - 1 <= (uint32)Info.MaxFreeRangeSize * MinAlignment)
325 {
326 FChunk& Chunk = Chunks[Info.ChunkIndex];
327 SIZE_T Ptr = Chunk.Alloc(Size, Alignment, Info.MaxFreeRangeSize, OutAllocInfo.RangeOffset, OutAllocInfo.RangeSize);
328 Check(Ptr != 0);
329#if RANGE_ALLOCATOR_RECORD_STATS
330 SizeUsed += OutAllocInfo.RangeSize * MinAlignment;
331#endif
332 OutAllocInfo.ChunkIndex = Info.ChunkIndex;
333 OutAllocInfo.InitCustomInfo(Chunk, Ptr);
334 if (Info.MaxFreeRangeSize < MinAllocSize)
335 {
336 Chunk.InfoIndex = INDEX_NONE;
338 }
339 return MakePointer(Ptr);
340 }
341 else if (Size <= (uint32)Info.MaxFreeRangeSize * MinAlignment)
342 {
344 }
345 }
346
347 // Check may fits
348 for (int32 Index = 0; Index < MayFitInfoIndices.Num(); ++Index)
349 {
350 const int32 InfoIndex = MayFitInfoIndices[Index];
351 FFreeChunkInfo& Info = FreeChunkInfos[InfoIndex];
352 FChunk& Chunk = Chunks[Info.ChunkIndex];
353 SIZE_T Ptr = Chunk.Alloc(Size, Alignment, Info.MaxFreeRangeSize, OutAllocInfo.RangeOffset, OutAllocInfo.RangeSize);
354 if (Ptr != 0)
355 {
356#if RANGE_ALLOCATOR_RECORD_STATS
357 SizeUsed += OutAllocInfo.RangeSize * MinAlignment;
358#endif
359 OutAllocInfo.ChunkIndex = Info.ChunkIndex;
360 OutAllocInfo.InitCustomInfo(Chunk, Ptr);
361 if (Info.MaxFreeRangeSize < MinAllocSize)
362 {
363 Chunk.InfoIndex = INDEX_NONE;
364 RemoveFreeChunkInfo(InfoIndex);
365 }
366 return MakePointer(Ptr);
367 }
368 }
369
370 // Alloc from a new chunk
371 uint16 MaxFreeRangeSize = ChunkSize / MinAlignment;
373 int32 ChunkIndex;
375 {
376 ChunkIndex = FreeChunkSlotHead;
377 FreeChunkSlotHead = Chunks[FreeChunkSlotHead].NextFreeChunkSlot;
378 }
379 else
380 {
381 ChunkIndex = Chunks.AddUninitialized();
382 }
383 FChunk& Chunk = Chunks[ChunkIndex];
384 ::new ((void*)&Chunk) FChunk(*this);
385 SIZE_T Ptr = Chunk.Alloc(Size, Alignment, MaxFreeRangeSize, OutAllocInfo.RangeOffset, OutAllocInfo.RangeSize);
386 Check(Ptr != 0);
387#if RANGE_ALLOCATOR_RECORD_STATS
388 SizeTotal += ChunkSize;
389 SizeUsed += OutAllocInfo.RangeSize * MinAlignment;
390#endif
391 OutAllocInfo.ChunkIndex = ChunkIndex;
392 OutAllocInfo.InitCustomInfo(Chunk, Ptr);
393 if (MaxFreeRangeSize >= MinAllocSize)
394 {
395 Chunk.InfoIndex = FreeChunkInfos.Add(FFreeChunkInfo(MaxFreeRangeSize, (uint16)ChunkIndex));
396 }
397 return MakePointer(Ptr);
398 }
399
401 {
403
404 const int32 InfoIndex = Chunks[AllocInfo.ChunkIndex].InfoIndex;
405 uint16 NewMaxFreeRangeSize = InfoIndex == INDEX_NONE ? 0 : FreeChunkInfos[InfoIndex].MaxFreeRangeSize;
406
407 Chunks[AllocInfo.ChunkIndex].Free(AllocInfo.RangeOffset, AllocInfo.RangeSize, NewMaxFreeRangeSize);
408#if RANGE_ALLOCATOR_RECORD_STATS
409 SizeUsed -= AllocInfo.RangeSize * MinAlignment;
410#endif
411 if (NewMaxFreeRangeSize == ChunkSize / MinAlignment)
412 {
413 FreeChunk(AllocInfo.ChunkIndex);
414 }
415 else if (InfoIndex == INDEX_NONE)
416 {
418 {
419 Check(AllocInfo.ChunkIndex <= UINT16_MAX);
421 }
422 }
423 else
424 {
425 FreeChunkInfos[InfoIndex].MaxFreeRangeSize = NewMaxFreeRangeSize;
426 }
427 }
428
430 {
432
433 for (const FChunk& Chunk : Chunks)
434 {
435 if (Chunk.IsValid() && Chunk.Contains(Addr))
436 {
437 return true;
438 }
439 }
440 return false;
441 }
442};
constexpr T Align(T Val, uint64 Alignment)
Definition AlignmentTemplates.h:18
@ INDEX_NONE
Definition CoreMiscDefines.h:150
FPlatformTypes::SIZE_T SIZE_T
An unsigned integer the same size as a pointer, the same as UPTRINT.
Definition Platform.h:1150
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
UE::FPlatformRecursiveMutex FCriticalSection
Definition CriticalSection.h:53
FRWLock Lock
Definition UnversionedPropertySerialization.cpp:921
uint32 Offset
Definition VulkanMemory.cpp:4033
uint32 Size
Definition VulkanMemory.cpp:4034
uint16_t uint16
Definition binka_ue_file_header.h:7
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition ScopeLock.h:141
Definition Array.h:670
UE_FORCEINLINE_HINT SizeType AddUninitialized()
Definition Array.h:1664
UE_REWRITE SizeType Num() const
Definition Array.h:1144
void RemoveAt(SizeType Index, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:2083
UE_FORCEINLINE_HINT void RemoveAtSwap(SizeType Index, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:2185
UE_NODEBUG UE_FORCEINLINE_HINT SizeType Add(ElementType &&Item)
Definition Array.h:2696
SizeType Insert(std::initializer_list< ElementType > InitList, const SizeType InIndex)
Definition Array.h:1875
Definition RangeAllocator.h:94
SIZE_T Alloc(uint32 InSize, uint32 InAlignment, uint16 &InOutMaxFreeRangeSize, uint16 &OutOffset, uint16 &OutSize)
Definition RangeAllocator.h:127
void Free(uint16 Offset, uint16 Size, uint16 &InOutMaxFreeRangeSize)
Definition RangeAllocator.h:195
FChunk(const FRangeAllocator &Allocator)
Definition RangeAllocator.h:120
int32 InfoIndex
Definition RangeAllocator.h:113
int32 NextFreeChunkSlot
Definition RangeAllocator.h:114
Definition RangeAllocator.h:82
void Free(const FAllocInfo &AllocInfo)
Definition RangeAllocator.h:400
TRangeAllocator(uint32 InMinAllocSize, ArgTypes &&... Args)
Definition RangeAllocator.h:301
SIZE_T SizeTotal
Definition RangeAllocator.h:279
typename FAllocatorModifier::FPointerType FPointerType
Definition RangeAllocator.h:85
TArray< FChunk > Chunks
Definition RangeAllocator.h:286
FCriticalSection CS
Definition RangeAllocator.h:287
bool Contains(FConstPointerType Addr) const
Definition RangeAllocator.h:429
typename FAllocatorModifier::FAllocInfoModifier FAllocInfoModifier
Definition RangeAllocator.h:88
void FreeChunk(uint32 ChunkIndex)
Definition RangeAllocator.h:263
const uint16 MinAllocSize
Definition RangeAllocator.h:283
typename FAllocatorModifier::FChunkModifier FChunkModifier
Definition RangeAllocator.h:87
SIZE_T SizeUsed
Definition RangeAllocator.h:280
typename FAllocatorModifier::FConstPointerType FConstPointerType
Definition RangeAllocator.h:86
int32 FreeChunkSlotHead
Definition RangeAllocator.h:284
TArray< FFreeChunkInfo > FreeChunkInfos
Definition RangeAllocator.h:285
FPointerType Alloc(uint32 Size, uint32 Alignment, FAllocInfo &OutAllocInfo)
Definition RangeAllocator.h:307
void RemoveFreeChunkInfo(int32 InfoIndex)
Definition RangeAllocator.h:254
UE_REWRITE auto UpperBoundBy(const RangeType &Range, const ValueType &Value, ProjectionType Projection, SortPredicateType SortPredicate) -> decltype(GetNum(Range))
Definition BinarySearch.h:154
U16 Index
Definition radfft.cpp:71
static constexpr UE_FORCEINLINE_HINT bool IsPowerOfTwo(T Value)
Definition UnrealMathUtility.h:519
Definition RangeAllocator.h:294
uint16 RangeOffset
Definition RangeAllocator.h:295
uint32 ChunkIndex
Definition RangeAllocator.h:297
uint16 RangeSize
Definition RangeAllocator.h:296
Definition RangeAllocator.h:244
FFreeChunkInfo(uint16 InMaxFreeRangeSize, uint16 InChunkIndex)
Definition RangeAllocator.h:248
uint16 ChunkIndex
Definition RangeAllocator.h:246
uint16 MaxFreeRangeSize
Definition RangeAllocator.h:245