UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
OSAllocationPool.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5// HEADER_UNIT_SKIP - Not included directly
6
7#if PLATFORM_HAS_FPlatformVirtualMemoryBlock
8/*
9 * Perform checks that ensure that the pools are working as intended. This is not necessary in builds that are used for Shipping/Test or for the Development editor.
10 */
11#define UE4_TMEMORY_POOL_DO_SANITY_CHECKS (UE_BUILD_DEBUG || (UE_BUILD_DEVELOPMENT && !UE_EDITOR))
12
20template<
22>
23class TMemoryPool
24{
25protected:
26
28 SIZE_T BlockSize;
29
32
35
37 SIZE_T NumBlocks;
38
41
44
46 SIZE_T NumFreeBlocks;
47
50
52
53#if UE4_TMEMORY_POOL_DO_SANITY_CHECKS
54 FThreadSafeCounter NoConcurrentAccess; // Tests that this is not being accessed on multiple threads at once, see TestPAL mallocthreadtest
55#endif // UE4_TMEMORY_POOL_DO_SANITY_CHECKS
56
57public:
58
60 : BlockSize(InBlockSize)
63 , NumBlocks(InNumBlocks)
66 , NumFreeBlocks(InNumBlocks)
69 {
70#if UE4_TMEMORY_POOL_DO_SANITY_CHECKS
71 checkf((AlignedPoolStart % RequiredAlignment) == 0, TEXT("Non-aligned pool address passed to a TMemoryPool"));
72#endif // UE4_TMEMORY_POOL_DO_SANITY_CHECKS
73
74 FMemory::Memset(Bitmask, 0xFF, BitmaskSizeInBytes);
75 //checkf(NumFreeBlocks == CalculateFreeBlocksInBitmap(), TEXT("Mismatch between a bitmap and NumFreeBlocks at the very beginning"));
76
77 // decommit all the memory
78 VMBlock.DecommitByPtr(reinterpret_cast<void *>(AlignedPoolStart), Align(NumBlocks * BlockSize, FPlatformMemory::FPlatformVirtualMemoryBlock::GetCommitAlignment()));
79 }
80
82 void* Allocate(SIZE_T Size)
83 {
84#if UE4_TMEMORY_POOL_DO_SANITY_CHECKS
85 checkf(Size <= BlockSize, TEXT("Attempting to allocate %llu bytes from a memory pool of %llu byte blocks"), (uint64)Size, (uint64)BlockSize);
86
87 checkf(NoConcurrentAccess.Increment() == 1, TEXT("TMemoryPool is being accessed on multiple threads. The class is not thread safe, add locking!."));
88#endif // UE4_TMEMORY_POOL_DO_SANITY_CHECKS
89
90 void* Address = nullptr;
91 if (LIKELY(NumFreeBlocks > 0))
92 {
93 //checkf(NumFreeBlocks == CalculateFreeBlocksInBitmap(), TEXT("Mismatch between a bitmap and NumFreeBlocks before Allocate()ing"));
94 Address = FindFirstFreeAndMarkUsed();
95 --NumFreeBlocks;
97 //checkf(NumFreeBlocks == CalculateFreeBlocksInBitmap(), TEXT("Mismatch between a bitmap and NumFreeBlocks after Allocate()ing"));
98
99#if UE4_TMEMORY_POOL_DO_SANITY_CHECKS
100 checkf(Address != nullptr, TEXT("NumFreeBlocks and bitmask of the free blocks are not in sync - bug in TMemoryPool"));
101#endif // UE4_TMEMORY_POOL_DO_SANITY_CHECKS
102
103 // make sure this address range is backed by the actual RAM
104 VMBlock.CommitByPtr(Address, Align(Size, FPlatformMemory::FPlatformVirtualMemoryBlock::GetCommitAlignment()));
105
106 }
107
108#if UE4_TMEMORY_POOL_DO_SANITY_CHECKS
109 checkf(NoConcurrentAccess.Decrement() == 0, TEXT("TMemoryPool is being accessed on multiple threads. The class is not thread safe, add locking!."));
110#endif // UE4_TMEMORY_POOL_DO_SANITY_CHECKS
111
112 return Address;
113 }
114
116 void Free(void *Ptr, SIZE_T Size)
117 {
118 // first, check if the block is ours at all. This may happen if allocations spilled over to regular BAFO() when the pool is full, but it is unlikely.
119#if UE4_TMEMORY_POOL_DO_SANITY_CHECKS
120 checkf(WasAllocatedFromThisPool(Ptr, BlockSize), TEXT("Address passed to Free() of a pool of block size %llu was not allocated in it (address: %p, boundaries: %p - %p"),
121 (uint64)BlockSize,
122 Ptr,
123 reinterpret_cast<void *>(AlignedPoolStart),
124 reinterpret_cast<void *>(AlignedPoolEnd)
125 );
126
127 checkf((reinterpret_cast<SIZE_T>(Ptr) % RequiredAlignment == 0), TEXT("Address passed to Free() of a pool of block size %llu was not aligned to %llu bytes (address: %p)"),
128 (uint64)BlockSize,
129 (uint64)RequiredAlignment,
130 Ptr
131 );
132
133 checkf(NoConcurrentAccess.Increment() == 1, TEXT("TMemoryPool is being accessed on multiple threads. The class is not thread safe, add locking!."));
134#endif // UE4_TMEMORY_POOL_DO_SANITY_CHECKS
135
136 // add the pointer back to the pool
137 //checkf(NumFreeBlocks == CalculateFreeBlocksInBitmap(), TEXT("Mismatch between a bitmap and NumFreeBlocks before Free()ing"));
138 MarkFree(Ptr);
139 NumFreeBlocks++;
141 //checkf(NumFreeBlocks == CalculateFreeBlocksInBitmap(), TEXT("Mismatch between a bitmap and NumFreeBlocks after Free()ing"));
142
143 // evict this memory
144 VMBlock.DecommitByPtr(Ptr, BlockSize);
145
146#if UE4_TMEMORY_POOL_DO_SANITY_CHECKS
147 // check that we never free more than we allocated
148 checkf(NumFreeBlocks <= NumBlocks, TEXT("Too many frees!"));
149
150 checkf(NoConcurrentAccess.Decrement() == 0, TEXT("TMemoryPool is being accessed on multiple threads. The class is not thread safe, add locking!."));
151#endif // UE4_TMEMORY_POOL_DO_SANITY_CHECKS
152 }
153
154 static SIZE_T BitmaskMemorySize(SIZE_T NumBlocks)
155 {
156 return (NumBlocks / 8) + ((NumBlocks & 7) ? 1 : 0);
157 }
158
159 void MarkFree(void *Ptr)
160 {
161 // calculate the bit number
162 SIZE_T PtrOffset = reinterpret_cast<SIZE_T>(Ptr);
163 SIZE_T BitIndex = (PtrOffset - AlignedPoolStart) / BlockSize;
164
165#if UE4_TMEMORY_POOL_DO_SANITY_CHECKS
166 checkf(BitIndex < NumBlocks, TEXT("Incorrect pointer %p passed to MarkFree()"), Ptr);
167#endif // UE4_TMEMORY_POOL_DO_SANITY_CHECKS
168
169 SIZE_T ByteIndex = BitIndex / 8;
170 uint8 Byte = Bitmask[ByteIndex];
171
172 int32 IndexInByte = static_cast<int32>(BitIndex & 0x7);
173#if UE4_TMEMORY_POOL_DO_SANITY_CHECKS
174 checkf((Byte & (1 << IndexInByte)) == 0, TEXT("MarkFree() - double freeing the pointer %p"), Ptr);
175#endif // UE4_TMEMORY_POOL_DO_SANITY_CHECKS
176 Byte |= (1 << IndexInByte);
177 Bitmask[ByteIndex] = Byte;
178 }
179
181 {
183
185 SIZE_T IdxQword = 0;
187 {
188 uint64 Qword = *reinterpret_cast<uint64*>(CurPtr);
189 if (UNLIKELY(Qword != 0))
190 {
191 // find the first free in it
192 // the memory is assumed to be little endian, so we count trailing
193 SIZE_T IndexOfFirstFreeInQword = FMath::CountTrailingZeros64(Qword);
194
195 // mark as used
196 Qword &= ~(1ULL << IndexOfFirstFreeInQword);
197 *reinterpret_cast<uint64*>(CurPtr) = Qword;
198
199 // return the address
201#if UE4_TMEMORY_POOL_DO_SANITY_CHECKS
202 checkf(IndexOfFirstBlock < NumBlocks, TEXT("Allocating outside of pool - TMemoryPool error."));
203#endif // UE4_TMEMORY_POOL_DO_SANITY_CHECKS
204 return reinterpret_cast<void *>(AlignedPoolStart + IndexOfFirstBlock * BlockSize);
205 }
206
207 CurPtr += 8;
208 ++IdxQword;
209 }
210
211 // search byte to byte
212 SIZE_T MaxIndexInLastByte = (NumBlocks & 0x7) ? (NumBlocks & 0x7) : 8;
213
215 while(CurPtr < BitmaskEnd)
216 {
217 uint8 Byte = *CurPtr;
218 if (UNLIKELY(Byte != 0))
219 {
220 // find the first free
221 SIZE_T IndexOfFirstFreeInByte = FMath::CountTrailingZeros(static_cast<int32>(Byte));
222
223 // the last byte needs a special attention
225 {
226 // mark as used
227 Byte &= ~(1 << IndexOfFirstFreeInByte);
228 *CurPtr = Byte;
229
230 // return the address
232#if UE4_TMEMORY_POOL_DO_SANITY_CHECKS
233 checkf(IndexOfFirstBlock < NumBlocks, TEXT("Allocating outside of pool - TMemoryPool error."));
234#endif // UE4_TMEMORY_POOL_DO_SANITY_CHECKS
235 return reinterpret_cast<void *>(AlignedPoolStart + IndexOfFirstBlock * BlockSize);
236 }
237 }
238
239 ++CurPtr;
240 }
241
242 return nullptr;
243 }
244
247 {
248 SIZE_T NumFree = 0;
249
252 while(CurPtr < BitmaskEnd - sizeof(uint64))
253 {
254 NumFree += FMath::CountBits(*reinterpret_cast<uint64*>(CurPtr));
255 CurPtr += sizeof(uint64);
256 }
257
258 while(CurPtr < BitmaskEnd - 1)
259 {
260 NumFree += FMath::CountBits(static_cast<uint64>(*CurPtr));
261 CurPtr ++;
262 }
263
264 // handle last byte manually
265 if ((NumBlocks & 0x7) == 0)
266 {
267 NumFree += FMath::CountBits(static_cast<uint64>(*CurPtr));
268 }
269 else
270 {
271 uint8 LastByte = *CurPtr;
272
273 SIZE_T MaxIndexInLastByte = (NumBlocks & 0x7);
274 for(SIZE_T Idx = 0; Idx < MaxIndexInLastByte; ++Idx)
275 {
276 NumFree += (LastByte & (1 << Idx)) ? 1 : 0;
277 }
278 }
279
280 return NumFree;
281 }
282
285 {
286 return BlockSize >= Size;
287 }
288
290 bool WasAllocatedFromThisPool(void* Ptr, SIZE_T Size)
291 {
292 // this extra size check is largely redundant and this function is on a hot path
293 return /*BlockSize >= Size &&*/ reinterpret_cast<SIZE_T>(Ptr) >= AlignedPoolStart && reinterpret_cast<SIZE_T>(Ptr) < AlignedPoolEnd;
294 }
295
296 bool IsEmpty() const
297 {
298 return NumFreeBlocks == NumBlocks;
299 }
300
303 {
304 return NumFreeBlocks * BlockSize;
305 }
306
308 uint64 GetOverheadSize() const
309 {
310 return (NumBlocks - NumFreeBlocks) * BlockSize - UsefulMemorySize;
311 }
312
313 void PrintDebugInfo()
314 {
315 printf("BlockSize: %llu NumAllocated/TotalBlocks = %llu/%llu\n", (uint64)BlockSize, (uint64)(NumBlocks - NumFreeBlocks), (uint64)NumBlocks);
316 }
317};
318
319#endif
constexpr T Align(T Val, uint64 Alignment)
Definition AlignmentTemplates.h:18
#define checkf(expr, format,...)
Definition AssertionMacros.h:315
#define LIKELY(x)
Definition CityHash.cpp:107
#define TEXT(x)
Definition Platform.h:1272
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
#define UNLIKELY(x)
Definition Platform.h:857
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
uint32 Size
Definition VulkanMemory.cpp:4034
uint8_t uint8
Definition binka_ue_file_header.h:8
Definition AndroidPlatformMemory.h:38
Definition ThreadSafeCounter.h:14
constexpr SIZE_T RequiredAlignment
Definition PimplPtr.h:55
@ Bitmask
[FunctionMetadata] [InterfaceMetadata] Metadata that identifies an integral property as a bitmask.
Definition ObjectMacros.h:1760
static UE_FORCEINLINE_HINT void * Memset(void *Dest, uint8 Char, SIZE_T Count)
Definition UnrealMemory.h:119