UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
MallocBinned3.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "CoreTypes.h"
6
7#if PLATFORM_HAS_FPlatformVirtualMemoryBlock
8#include "AutoRTFM.h"
13#include "HAL/PlatformMemory.h"
14#include "HAL/PlatformMutex.h"
15#include "HAL/UnrealMemory.h"
16#include "Math/NumericLimits.h"
19#include "Templates/Atomic.h"
20
21
22#ifndef UE_MB3_USE_CACHED_PAGE_ALLOCATOR_FOR_LARGE_ALLOCS
23# define UE_MB3_USE_CACHED_PAGE_ALLOCATOR_FOR_LARGE_ALLOCS (0)
24#endif
25
26#if PLATFORM_IOS
27# define UE_MB3_MAX_MEMORY_PER_POOL_SIZE_MB 512
28#elif !defined(UE_MB3_MAX_MEMORY_PER_POOL_SIZE_MB)
29# define UE_MB3_MAX_MEMORY_PER_POOL_SIZE_MB 1024
30#endif
31
32#if defined(USE_512MB_MAX_MEMORY_PER_BLOCK_SIZE)
33# warning "USE_512MB_MAX_MEMORY_PER_BLOCK_SIZE is deprecated. Please use UE_MB3_MAX_MEMORY_PER_POOL_SIZE_MB=%MB% instead"
34#endif
35
36#define UE_MB3_BASE_PAGE_SIZE 4096 // Minimum "page size" for binned3
37
38
39#ifndef UE_MB3_MAX_SMALL_POOL_SIZE
40# if UE_MB3_USE_CACHED_PAGE_ALLOCATOR_FOR_LARGE_ALLOCS
41# define UE_MB3_MAX_SMALL_POOL_SIZE (UE_MBC_MAX_LISTED_SMALL_POOL_SIZE) // Maximum small bin size
42# else
43# define UE_MB3_MAX_SMALL_POOL_SIZE (128 * 1024) // Maximum small bin size
44# endif
45#endif
46#define UE_MB3_SMALL_POOL_COUNT (UE_MBC_NUM_LISTED_SMALL_POOLS + (UE_MB3_MAX_SMALL_POOL_SIZE - UE_MBC_MAX_LISTED_SMALL_POOL_SIZE) / UE_MB3_BASE_PAGE_SIZE)
47
48#define UE_MB3_MAX_MEMORY_PER_POOL_SIZE_SHIFT (FMath::CountTrailingZeros(FMath::RoundUpToPowerOfTwo(UE_MB3_MAX_MEMORY_PER_POOL_SIZE_MB * 1024 * 1024)))
49
50#define UE_MB3_MAX_MEMORY_PER_POOL_SIZE (1ull << UE_MB3_MAX_MEMORY_PER_POOL_SIZE_SHIFT)
51
52// This choice depends on how efficient the OS is with sparse commits in large VM blocks
53#if !defined(BINNED3_USE_SEPARATE_VM_PER_POOL)
54# if PLATFORM_WINDOWS
55# define BINNED3_USE_SEPARATE_VM_PER_POOL (1)
56# else
57# define BINNED3_USE_SEPARATE_VM_PER_POOL (0)
58# endif
59#endif
60
61#define UE_MB3_ALLOCATOR_STATS UE_MBC_ALLOCATOR_STATS
62
63#if UE_MB3_ALLOCATOR_STATS
64# define UE_M3_ALLOCATOR_PER_BIN_STATS !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
65#else
66# define UE_M3_ALLOCATOR_PER_BIN_STATS 0
67#endif
68
69
71
72//
73// Optimized virtual memory allocator.
74//
75// MallocBinned3 supports two types of allocations - large and small pool allocation:
76// 1. For small pool allocation MallocBinned3 reserves contiguous range of virtual memory (a Pool) for each allocation size (a Bin).
77// These Pools can be adjacent to each other in memory if BINNED3_USE_SEPARATE_VM_PER_POOL is set to 0.
78// By default each Pool reserves 1 GB of address space, unless USE_512MB_MAX_MEMORY_PER_BLOCK_SIZE is defined to 1.
79// Each Pool commits and decommits reserved memory in Blocks. Each Block is equal to at least one memory page in it's size.
80// A Block contains N Bins in a way to minimize the tail memory waste. For example 16 bytes bins fit inside 4KB memory page without any waste;
81// i.e. one Block for 16 byte Bins contains 256 bins. However 736 bytes Bin uses two 4KB pages in it's Block to minimize memory waste.
82// Each Pool manages it's Blocks allocation via a Bit Tree (BlocksAllocatedBits\BlocksExhaustedBits members).
83// And every Block manages it's Bins via a number of additional data structures - FPoolInfoSmall and FFreeBlock.
84// FFreeBlock is an in-place header for the Block that's stored at the end of each block and contains info on number of free Bins and index of the next free Block if any
85// Memory is allocated top down in a Block
86//
87// 2. For large allocations we go directly to OS, unless UE_MB3_USE_CACHED_PAGE_ALLOCATOR_FOR_LARGE_ALLOCS is defined to 1
88// Each allocation is handled via FPlatformVirtualMemoryBlock and information about it is stored in FPoolInfo
89// FPoolInfo stores the original allocation size and how much memory was committed by the OS in case we need to realloc that allocation and there's enough tail waste to do that in place
90// All FPoolInfo are stored in the PoolHashBucket and HashBucketFreeList
91//
92class FMallocBinned3 : public TMallocBinnedCommon<FMallocBinned3, UE_MB3_SMALL_POOL_COUNT, UE_MB3_MAX_SMALL_POOL_SIZE>
93{
94public:
95 struct FPoolInfo
96 {
97 enum class ECanary : uint32
98 {
99 Unassigned = 0x39431234,
100 Assigned = 0x17ea5678,
101 };
102
103 FPoolInfo();
104
105 void CheckCanary(ECanary ShouldBe) const;
106 void SetCanary(ECanary ShouldBe, bool bPreexisting, bool bGuarnteedToBeNew);
107
108 uint32 GetOSRequestedBytes() const;
110
111 // And alias for GetOsCommittedBytes() to align with MB2 naming convention
112 uint32 GetOsAllocatedBytes() const
113 {
114 return GetOsCommittedBytes();
115 }
116
117 uint32 GetOsVMPages() const;
120
121 ECanary Canary;
122 private:
123 uint32 AllocSize; // Number of bytes allocated
124 uint32 VMSizeDivVirtualSizeAlignment; // Number of VM bytes allocated aligned for OS
125 uint32 CommitSize; // Number of bytes committed by the OS
126 };
127
128 // Forward declarations
129 struct FPoolInfoSmall;
130 struct FPoolTable;
131 struct Private;
132
134 struct FPoolTable
135 {
136 uint32 BinSize; // Bin size, i.e. 16, 32, 64 etc bytes. Pretty redundant and can be computed
137 uint32 BlockSize; // Size of a block. A block contains N bins in a way to minimize tail memory waste
138 uint32 NumMemoryPagesPerBlock; // Num memory pages needed to allocate one block.
140
141 FBitTree BlocksAllocatedBits; // A bit per per block, one indicates a committed block
142 FBitTree BlocksExhaustedBits; // One here means that the block is completely full
143
144 FPoolInfoSmall** PoolInfos; // Book keeping info about every allocated area for this bin's pool
145
146 uint64 UnusedAreaOffsetLow = 0; // High watermark for allocated vm memory for this pool
147
148#if UE_MB3_ALLOCATOR_STATS
149 uint32 TotalUsedBins = 0; // Used to calculated load factor, i.e.:
150 uint32 TotalAllocatedBins = 0; // used bins divided by total bins number in all allocated blocks
152#endif
153
155
156#if UE_M3_ALLOCATOR_PER_BIN_STATS
157 // these are "head end" stats, above the TLS cache
158 std::atomic<int64> TotalRequestedAllocSize = 0;
159 std::atomic<int64> TotalAllocCount = 0;
160 std::atomic<int64> TotalFreeCount = 0;
161
162 inline void HeadEndAlloc(SIZE_T Size)
163 {
164 check(Size >= 0 && Size <= BinSize);
165 TotalRequestedAllocSize.fetch_add(Size, std::memory_order_relaxed);
166 TotalAllocCount.fetch_add(1, std::memory_order_relaxed);
167 }
169 {
170 TotalFreeCount.fetch_add(1, std::memory_order_relaxed);
171 }
172#else
174 {
175 }
177 {
178 }
179#endif
180 };
181
182 // Pool tables for different pool sizes
183 FPoolTable SmallPoolTables[UE_MB3_SMALL_POOL_COUNT];
184
185private:
187
188#if UE_MB3_USE_CACHED_PAGE_ALLOCATOR_FOR_LARGE_ALLOCS
190#endif
191
192#if !BINNED3_USE_SEPARATE_VM_PER_POOL
193 FORCEINLINE uint64 PoolIndexFromPtr(const void* Ptr) const // returns a uint64 for it can also be used to check if it is an OS allocation
194 {
196 }
198 {
200 }
201#else
202# if UE_MB3_ALLOCATOR_STATS
203 void RecordPoolSearch(uint32 Tests) const;
204# else
205 FORCEINLINE void RecordPoolSearch(uint32 Tests) const
206 {
207
208 }
209# endif
210
211 inline uint64 PoolIndexFromPtr(const void* Ptr) const // returns a uint64 for it can also be used to check if it is an OS allocation
212 {
213 if (PoolSearchDiv == 0)
214 {
216 }
219 {
220 PoolIndex = uint64((uint8*)Ptr - PoolBaseVMPtr[0]) / PoolSearchDiv;
221 if (PoolIndex >= UE_MB3_SMALL_POOL_COUNT)
222 {
223 PoolIndex = UE_MB3_SMALL_POOL_COUNT - 1;
224 }
225 uint32 Tests = 1; // we are counting potential cache misses here, not actual comparisons
226 if ((uint8*)Ptr < PoolBaseVMPtr[PoolIndex])
227 {
228 do
229 {
230 Tests++;
231 PoolIndex--;
232 check(PoolIndex < UE_MB3_SMALL_POOL_COUNT);
233 } while ((uint8*)Ptr < PoolBaseVMPtr[PoolIndex]);
234 if ((uint8*)Ptr >= PoolBaseVMPtr[PoolIndex] + UE_MB3_MAX_MEMORY_PER_POOL_SIZE)
235 {
236 PoolIndex = UE_MB3_SMALL_POOL_COUNT; // was in the gap
237 }
238 }
239 else if ((uint8*)Ptr >= PoolBaseVMPtr[PoolIndex] + UE_MB3_MAX_MEMORY_PER_POOL_SIZE)
240 {
241 do
242 {
243 Tests++;
244 PoolIndex++;
245 check(PoolIndex < UE_MB3_SMALL_POOL_COUNT);
246 } while ((uint8*)Ptr >= PoolBaseVMPtr[PoolIndex] + UE_MB3_MAX_MEMORY_PER_POOL_SIZE);
247 if ((uint8*)Ptr < PoolBaseVMPtr[PoolIndex])
248 {
249 PoolIndex = UE_MB3_SMALL_POOL_COUNT; // was in the gap
250 }
251 }
252 RecordPoolSearch(Tests);
253 }
254 return PoolIndex;
255 }
256
258 {
260 }
261#endif //~!BINNED3_USE_SEPARATE_VM_PER_POOL
262
263 inline uint32 PoolIndexFromPtrChecked(const void* Ptr) const
264 {
265 const uint64 Result = PoolIndexFromPtr(Ptr);
267 return (uint32)Result;
268 }
269
270 FORCEINLINE bool IsOSAllocation(const void* Ptr) const
271 {
273 }
274
275 inline void* BlockPointerFromContainedPtr(const void* Ptr, uint8 NumMemoryPagesPerBlock, uint32& OutBlockIndex) const
276 {
277 const uint32 PoolIndex = PoolIndexFromPtrChecked(Ptr);
278 uint8* PoolStart = PoolBasePtr(PoolIndex);
279 const uint64 BlockIndex = (UPTRINT(Ptr) - UPTRINT(PoolStart)) / (UPTRINT(NumMemoryPagesPerBlock) * UPTRINT(OsAllocationGranularity));
281
282 uint8* Result = PoolStart + BlockIndex * UPTRINT(NumMemoryPagesPerBlock) * UPTRINT(OsAllocationGranularity);
283
285 return Result;
286 }
287
289 {
291 uint8* Ptr = PoolStart + BlockIndex * uint64(BlockSize);
292 check(Ptr + BlockSize <= PoolStart + UE_MB3_MAX_MEMORY_PER_POOL_SIZE);
293 return Ptr;
294 }
295
296 FPoolInfoSmall* PushNewPoolToFront(FPoolTable& Table, uint32 InBinSize, uint32 InPoolIndex, uint32& OutBlockIndex);
297 FPoolInfoSmall* GetFrontPool(FPoolTable& Table, uint32 InPoolIndex, uint32& OutBlockIndex);
298
299public:
300
302
303 virtual ~FMallocBinned3();
304
305 // FMalloc interface.
306 virtual bool IsInternallyThreadSafe() const override;
307
309 virtual void* Malloc(SIZE_T Size, uint32 Alignment) override;
310
312 virtual void* Realloc(void* Ptr, SIZE_T NewSize, uint32 Alignment) override;
313
315 FORCEINLINE virtual void Free(void* Ptr) override
316 {
317 FreeExternal(Ptr, PoolIndexFromPtr(Ptr));
318 }
319
320 inline bool GetSmallAllocationSize(void* Ptr, SIZE_T& SizeOut) const
321 {
322 const uint64 PoolIndex = PoolIndexFromPtr(Ptr);
323 if (PoolIndex < UE_MB3_SMALL_POOL_COUNT)
324 {
325 SizeOut = PoolIndexToBinSize(PoolIndex);
326 return true;
327 }
328
329 return false;
330 }
331
332 inline virtual bool GetAllocationSize(void *Ptr, SIZE_T &SizeOut) override
333 {
334 if (GetSmallAllocationSize(Ptr, SizeOut))
335 {
336 return true;
337 }
339 }
340
341 FORCEINLINE virtual SIZE_T QuantizeSize(SIZE_T Count, uint32 Alignment) override
342 {
343 return QuantizeSizeCommon(Count, Alignment, *this);
344 }
345
346 virtual bool ValidateHeap() override;
347 virtual void Trim(bool bTrimThreadCaches) override;
348 virtual const TCHAR* GetDescriptiveName() override;
350 virtual void DumpAllocatorStats(class FOutputDevice& Ar) override;
351 virtual void UpdateStats() override
352 {
353 UpdateStatsCommon(*this);
354 }
355 // End FMalloc interface.
356
357 void FreeExternal(void* Ptr, uint64 PoolIndex);
358
359 // +1 enables PoolIndexToBinSize(~0u / -1) dummy access that helps avoid PoolIndex == 0 branching in Realloc(),
360 // see ((!PoolIndex) | (NewSize > PoolIndexToBinSize(static_cast<uint32>(PoolIndex - 1)))
363
364#if !BINNED3_USE_SEPARATE_VM_PER_POOL
365 static uint8* Binned3BaseVMPtr;
367#else
368 static uint64 PoolSearchDiv; // if this is zero, the VM turned out to be contiguous anyway so we use a simple subtract and shift
369 static uint8* HighestPoolBaseVMPtr; // this is a duplicate of PoolBaseVMPtr[UE_MB3_SMALL_POOL_COUNT - 1]
372#endif
373 // Mapping of sizes to small table indices
374 static uint8 MemSizeToPoolIndex[SIZE_TO_POOL_INDEX_NUM];
375
376 FORCEINLINE uint32 PoolIndexToBinSize(uint32 PoolIndex) const
377 {
378 return uint32(SmallBinSizesShifted[PoolIndex + 1]) << UE_MBC_BIN_SIZE_SHIFT;
379 }
380
381 void FreeBundles(FBundleNode* Bundles, uint32 PoolIndex);
382
383 void Commit(uint32 InPoolIndex, void *Ptr, SIZE_T Size);
384 void Decommit(uint32 InPoolIndex, void *Ptr, SIZE_T Size);
385
386 void FlushCurrentThreadCacheInternal(bool bNewEpochOnly = false);
387
388 static void* AllocateMetaDataMemory(SIZE_T Size);
389 static void FreeMetaDataMemory(void *Ptr, SIZE_T Size);
390};
391
393
394#if UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_7
395# include "HAL/CriticalSection.h"
396#endif
397
398#endif //~PLATFORM_HAS_FPlatformVirtualMemoryBlock
#define FORCEINLINE
Definition AndroidPlatform.h:140
@ Unassigned
Definition AppleControllerInterface.h:15
#define check(expr)
Definition AssertionMacros.h:314
#define UE_AUTORTFM_NOAUTORTFM
Definition AutoRTFMDefines.h:113
FPlatformTypes::SIZE_T SIZE_T
An unsigned integer the same size as a pointer, the same as UPTRINT.
Definition Platform.h:1150
FPlatformTypes::TCHAR TCHAR
Either ANSICHAR or WIDECHAR, depending on whether the platform supports wide characters or the requir...
Definition Platform.h:1135
FPlatformTypes::UPTRINT UPTRINT
An unsigned integer the same size as a pointer.
Definition Platform.h:1146
FPlatformTypes::uint64 uint64
A 64-bit unsigned integer.
Definition Platform.h:1117
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
#define UE_MBC_BIN_SIZE_SHIFT
Definition MallocBinnedCommon.h:49
#define PRAGMA_DISABLE_UNSAFE_TYPECAST_WARNINGS
Definition MSVCPlatformCompilerPreSetup.h:81
#define PRAGMA_RESTORE_UNSAFE_TYPECAST_WARNINGS
Definition MSVCPlatformCompilerPreSetup.h:100
uint32 Size
Definition VulkanMemory.cpp:4034
int BlockIndex
Definition binka_ue_decode_test.cpp:38
uint8_t uint8
Definition binka_ue_file_header.h:8
uint16_t uint16
Definition binka_ue_file_header.h:7
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition AndroidPlatformMemory.h:38
Definition MallocBinnedCommon.h:120
virtual const TCHAR * GetDescriptiveName()
Definition MemoryBase.h:248
virtual bool GetAllocationSize(void *Original, SIZE_T &SizeOut)
Definition MemoryBase.h:158
virtual void * Malloc(SIZE_T Count, uint32 Alignment=DEFAULT_ALIGNMENT)=0
virtual bool ValidateHeap()
Definition MemoryBase.h:238
virtual bool IsInternallyThreadSafe() const
Definition MemoryBase.h:230
virtual void DumpAllocatorStats(class FOutputDevice &Ar)
Definition MemoryBase.h:221
virtual void Trim(bool bTrimThreadCaches)
Definition MemoryBase.h:166
virtual void * Realloc(void *Original, SIZE_T Count, uint32 Alignment=DEFAULT_ALIGNMENT)=0
virtual CORE_API void UpdateStats()
Definition MemoryBase.cpp:72
virtual SIZE_T QuantizeSize(SIZE_T Count, uint32 Alignment)
Definition MemoryBase.h:146
Definition OutputDevice.h:133
Definition MallocBinnedCommon.h:452
SIZE_T QuantizeSizeCommon(SIZE_T Count, uint32 Alignment, const AllocType &Alloc) const
Definition MallocBinnedCommon.h:836
void UpdateStatsCommon(const AllocType &Alloc)
Definition MallocBinnedCommon.h:956
bool GetAllocationSizeExternal(void *Ptr, SIZE_T &SizeOut)
Definition MallocBinnedCommon.h:909
UE::FRecursiveMutex Mutex
Definition MeshPaintVirtualTexture.cpp:164
Definition OverriddenPropertySet.cpp:45
UE_STRING_CLASS Result(Forward< LhsType >(Lhs), RhsLen)
Definition String.cpp.inl:732
FPThreadsRecursiveMutex FPlatformRecursiveMutex
Definition AndroidPlatformMutex.h:12