Ark Server API (ASA) - Wiki
Loading...
Searching...
No Matches
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
13/**
14* Class for managing allocations of the size no larger than BlockSize.
15*
16* @param CommitAddressRange function to let the OS know that a range needs to be backed by physical RAM
17* @param EvictAddressRange function to let the OS know that the RAM pages backing an address range can be evicted.
18* @param RequiredAlignment alignment for the addresses returned from this pool
19*/
20template<
21 SIZE_T RequiredAlignment
22>
23class TMemoryPool
24{
25protected:
26
27 /** Size of a single block */
28 SIZE_T BlockSize;
29
30 /** Beginning of the pool (an address in memory), cast to SIZE_T for arithmetic ops. */
31 SIZE_T AlignedPoolStart;
32
33 /** End of the pool (an address in memory), cast to SIZE_T for arithmetic ops. */
34 SIZE_T AlignedPoolEnd;
35
36 /** Num of the blocks to cache */
37 SIZE_T NumBlocks;
38
39 /** A bit mask of the free blocks: 0 used, 1 free - because that way it's easier to scan */
40 uint8* Bitmask;
41
42 /** Size of the bitmask in bytes */
43 SIZE_T BitmaskSizeInBytes;
44
45 /** Current length of the stack. */
46 SIZE_T NumFreeBlocks;
47
48 /** When we're allocating less than block size, only BlockSize - Size is going to be used. */
49 SIZE_T UsefulMemorySize;
50
51 FPlatformMemory::FPlatformVirtualMemoryBlock VMBlock;
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
59 TMemoryPool(SIZE_T InBlockSize, SIZE_T InAlignedPoolStart, SIZE_T InNumBlocks, uint8* InBitmask, FPlatformMemory::FPlatformVirtualMemoryBlock& InVMBlock)
60 : BlockSize(InBlockSize)
61 , AlignedPoolStart(InAlignedPoolStart)
62 , AlignedPoolEnd(InAlignedPoolStart + InBlockSize * (SIZE_T)InNumBlocks)
63 , NumBlocks(InNumBlocks)
64 , Bitmask(InBitmask)
65 , BitmaskSizeInBytes(BitmaskMemorySize(NumBlocks))
66 , NumFreeBlocks(InNumBlocks)
67 , UsefulMemorySize(0)
68 , VMBlock(InVMBlock)
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
81 /** We always allocate in BlockSize chunks, Size is only passed for more accurate Commit() */
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;
96 UsefulMemorySize += Size;
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
115 /** We always free BlockSize-d chunks. */
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++;
140 UsefulMemorySize -= Size;
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
180 void* FindFirstFreeAndMarkUsed()
181 {
182 uint8* CurPtr = Bitmask;
183
184 SIZE_T BitmaskSizeInQWords = BitmaskSizeInBytes / 8;
185 SIZE_T IdxQword = 0;
186 while(IdxQword < BitmaskSizeInQWords)
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
200 SIZE_T IndexOfFirstBlock = static_cast<SIZE_T>(CurPtr - Bitmask) * 8 + IndexOfFirstFreeInQword;
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
214 uint8* BitmaskEnd = Bitmask + BitmaskSizeInBytes;
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
224 if (LIKELY(CurPtr < BitmaskEnd - 1 || IndexOfFirstFreeInByte < MaxIndexInLastByte))
225 {
226 // mark as used
227 Byte &= ~(1 << IndexOfFirstFreeInByte);
228 *CurPtr = Byte;
229
230 // return the address
231 SIZE_T IndexOfFirstBlock = static_cast<SIZE_T>(CurPtr - Bitmask) * 8 + IndexOfFirstFreeInByte;
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
245 /** Debugging function */
246 SIZE_T CalculateFreeBlocksInBitmap()
247 {
248 SIZE_T NumFree = 0;
249
250 uint8* CurPtr = Bitmask;
251 uint8* BitmaskEnd = Bitmask + BitmaskSizeInBytes;
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
283 /** Returns true if we can allocate this much memory from this pool. */
284 bool CanAllocateFromThisPool(SIZE_T Size)
285 {
286 return BlockSize >= Size;
287 }
288
289 /** Returns true if this allocation came from this pool. */
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
301 /** Returns memory size that we can actually allocate from the pool (mostly for malloc stats) */
302 uint64 GetAllocatableMemorySize() const
303 {
304 return NumFreeBlocks * BlockSize;
305 }
306
307 /** Returns overhead caused by allocating less than BlockSize (mostly for malloc stats) */
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