Ark Server API (ASA) - Wiki
Loading...
Searching...
No Matches
MallocBinnedArena.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "Containers/Array.h"
6#include "CoreTypes.h"
7#include "HAL/PlatformAtomics.h"
8#include "HAL/PlatformMemory.h"
9#include "Math/UnrealMathUtility.h"
10#include "Templates/Atomic.h"
11#include "Templates/MemoryOps.h"
12
14#include "HAL/Allocators/CachedOSPageAllocator.h"
15#include "HAL/Allocators/PooledVirtualMemoryAllocator.h"
16#include "HAL/CriticalSection.h"
17#include "HAL/LowLevelMemTracker.h"
18#include "HAL/MallocBinnedCommon.h"
19#include "HAL/MemoryBase.h"
20#include "HAL/PlatformMath.h"
21#include "HAL/PlatformTLS.h"
22#include "HAL/UnrealMemory.h"
23#include "Math/NumericLimits.h"
24#include "Misc/AssertionMacros.h"
25#include "Misc/ScopeLock.h"
26#include "Misc/ScopeLock.h"
27#include "Templates/AlignmentTemplates.h"
28
29
30#define BINNEDARENA_MAX_GMallocBinnedArenaMaxBundlesBeforeRecycle (8)
31
32#define COLLECT_BINNEDARENA_STATS (!UE_BUILD_SHIPPING)
33
34#if COLLECT_BINNEDARENA_STATS
35#define MBA_STAT(x) x
36#else
37#define MBA_STAT(x)
38#endif
39
40PRAGMA_DISABLE_UNSAFE_TYPECAST_WARNINGS
41
42class FMallocBinnedArena final : public FMalloc
43{
44 struct FGlobalRecycler;
45 struct FPoolInfoLarge;
46 struct FPoolInfoSmall;
47 struct FPoolTable;
48 struct PoolHashBucket;
49 struct Private;
50
51
52 struct FFreeBlock
53 {
54 enum
55 {
56 CANARY_VALUE = 0xc3
57 };
58
59 FORCEINLINE FFreeBlock(uint32 InPageSize, uint32 InBlockSize, uint32 InPoolIndex, uint8 MinimumAlignmentShift)
60 : BlockSizeShifted(InBlockSize >> MinimumAlignmentShift)
61 , PoolIndex(InPoolIndex)
62 , Canary(CANARY_VALUE)
63 , NextFreeIndex(MAX_uint32)
64 {
65 check(InPoolIndex < MAX_uint8 && (InBlockSize >> MinimumAlignmentShift) <= MAX_uint16);
66 NumFreeBlocks = InPageSize / InBlockSize;
67 }
68
69 FORCEINLINE uint32 GetNumFreeRegularBlocks() const
70 {
71 return NumFreeBlocks;
72 }
73 FORCEINLINE bool IsCanaryOk() const
74 {
75 return Canary == FFreeBlock::CANARY_VALUE;
76 }
77
78 FORCEINLINE void CanaryTest() const
79 {
80 if (!IsCanaryOk())
81 {
82 CanaryFail();
83 }
84 }
85 void CanaryFail() const;
86
87 FORCEINLINE void* AllocateRegularBlock(uint8 MinimumAlignmentShift)
88 {
89 --NumFreeBlocks;
90 return (uint8*)this + NumFreeBlocks * (uint32(BlockSizeShifted) << MinimumAlignmentShift);
91 }
92
93 uint16 BlockSizeShifted; // Size of the blocks that this list points to >> ArenaParams.MinimumAlignmentShift
94 uint8 PoolIndex; // Index of this pool
95 uint8 Canary; // Constant value of 0xe3
96 uint32 NumFreeBlocks; // Number of consecutive free blocks here, at least 1.
97 uint32 NextFreeIndex; // Next free block or MAX_uint32
98 };
99
100 struct FPoolTable
101 {
102 uint32 BlockSize;
103 uint16 BlocksPerBlockOfBlocks;
104 uint8 PagesPlatformForBlockOfBlocks;
105
106 FBitTree BlockOfBlockAllocationBits; // one bits in here mean the virtual memory is committed
107 FBitTree BlockOfBlockIsExhausted; // one bit in here means the pool is completely full
108
109 uint32 NumEverUsedBlockOfBlocks;
110 FPoolInfoSmall** PoolInfos;
111
112 uint64 UnusedAreaOffsetLow;
113 };
114
115 struct FPtrToPoolMapping
116 {
117 FPtrToPoolMapping()
118 : PtrToPoolPageBitShift(0)
119 , HashKeyShift(0)
120 , PoolMask(0)
121 , MaxHashBuckets(0)
122 {
123 }
124 explicit FPtrToPoolMapping(uint32 InPageSize, uint64 InNumPoolsPerPage, uint64 AddressLimit)
125 {
126 Init(InPageSize, InNumPoolsPerPage, AddressLimit);
127 }
128
129 void Init(uint32 InPageSize, uint64 InNumPoolsPerPage, uint64 AddressLimit)
130 {
131 uint64 PoolPageToPoolBitShift = FPlatformMath::CeilLogTwo(InNumPoolsPerPage);
132
133 PtrToPoolPageBitShift = FPlatformMath::CeilLogTwo(InPageSize);
134 HashKeyShift = PtrToPoolPageBitShift + PoolPageToPoolBitShift;
135 PoolMask = (1ull << PoolPageToPoolBitShift) - 1;
136 MaxHashBuckets = AddressLimit >> HashKeyShift;
137 }
138
139 FORCEINLINE void GetHashBucketAndPoolIndices(const void* InPtr, uint32& OutBucketIndex, UPTRINT& OutBucketCollision, uint32& OutPoolIndex) const
140 {
141 OutBucketCollision = (UPTRINT)InPtr >> HashKeyShift;
142 OutBucketIndex = uint32(OutBucketCollision & (MaxHashBuckets - 1));
143 OutPoolIndex = ((UPTRINT)InPtr >> PtrToPoolPageBitShift) & PoolMask;
144 }
145
146 FORCEINLINE uint64 GetMaxHashBuckets() const
147 {
148 return MaxHashBuckets;
149 }
150
151 private:
152 /** Shift to apply to a pointer to get the reference from the indirect tables */
153 uint64 PtrToPoolPageBitShift;
154 /** Shift required to get required hash table key. */
155 uint64 HashKeyShift;
156 /** Used to mask off the bits that have been used to lookup the indirect table */
157 uint64 PoolMask;
158 // PageSize dependent constants
159 uint64 MaxHashBuckets;
160 };
161
162 struct FBundleNode
163 {
164 FBundleNode* NextNodeInCurrentBundle;
165 union
166 {
167 FBundleNode* NextBundle;
168 int32 Count;
169 };
170 };
171
172 struct FBundle
173 {
174 FORCEINLINE FBundle()
175 {
176 Reset();
177 }
178
179 FORCEINLINE void Reset()
180 {
181 Head = nullptr;
182 Count = 0;
183 }
184
185 FORCEINLINE void PushHead(FBundleNode* Node)
186 {
187 Node->NextNodeInCurrentBundle = Head;
188 Node->NextBundle = nullptr;
189 Head = Node;
190 Count++;
191 }
192
193 FORCEINLINE FBundleNode* PopHead()
194 {
195 FBundleNode* Result = Head;
196
197 Count--;
198 Head = Head->NextNodeInCurrentBundle;
199 return Result;
200 }
201
202 FBundleNode* Head;
203 uint32 Count;
204 };
205
206 struct FFreeBlockList
207 {
208 // return true if we actually pushed it
209 FORCEINLINE bool PushToFront(void* InPtr, uint32 InPoolIndex, uint32 InBlockSize, const FArenaParams& LocalArenaParams)
210 {
211 checkSlow(InPtr);
212
213 if ((PartialBundle.Count >= (uint32)LocalArenaParams.MaxBlocksPerBundle) | (PartialBundle.Count * InBlockSize >= (uint32)LocalArenaParams.MaxSizePerBundle))
214 {
215 if (FullBundle.Head)
216 {
217 return false;
218 }
219 FullBundle = PartialBundle;
220 PartialBundle.Reset();
221 }
222 PartialBundle.PushHead((FBundleNode*)InPtr);
223 return true;
224 }
225 FORCEINLINE bool CanPushToFront(uint32 InPoolIndex, uint32 InBlockSize, const FArenaParams& LocalArenaParams)
226 {
227 return !((!!FullBundle.Head) & ((PartialBundle.Count >= (uint32)LocalArenaParams.MaxBlocksPerBundle) | (PartialBundle.Count * InBlockSize >= (uint32)LocalArenaParams.MaxSizePerBundle)));
228 }
229 FORCEINLINE void* PopFromFront(uint32 InPoolIndex)
230 {
231 if ((!PartialBundle.Head) & (!!FullBundle.Head))
232 {
233 PartialBundle = FullBundle;
234 FullBundle.Reset();
235 }
236 return PartialBundle.Head ? PartialBundle.PopHead() : nullptr;
237 }
238
239 // tries to recycle the full bundle, if that fails, it is returned for freeing
240 FBundleNode* RecyleFull(FArenaParams& LocalArenaParams, FGlobalRecycler& GGlobalRecycler, uint32 InPoolIndex);
241 bool ObtainPartial(FArenaParams& LocalArenaParams, FGlobalRecycler& GGlobalRecycler, uint32 InPoolIndex);
242 FBundleNode* PopBundles(uint32 InPoolIndex);
243 private:
244 FBundle PartialBundle;
245 FBundle FullBundle;
246 };
247
248 struct FPerThreadFreeBlockLists
249 {
250 FORCEINLINE static FPerThreadFreeBlockLists* Get(uint32 BinnedArenaTlsSlot)
251 {
252 return BinnedArenaTlsSlot ? (FPerThreadFreeBlockLists*)FPlatformTLS::GetTlsValue(BinnedArenaTlsSlot) : nullptr;
253 }
254 static void SetTLS(FMallocBinnedArena& Allocator);
255 static int64 ClearTLS(FMallocBinnedArena& Allocator);
256
257 FPerThreadFreeBlockLists(uint32 PoolCount)
258 : AllocatedMemory(0)
259 {
260 FreeLists.AddDefaulted(PoolCount);
261 }
262
263 FORCEINLINE void* Malloc(uint32 InPoolIndex)
264 {
265 return FreeLists[InPoolIndex].PopFromFront(InPoolIndex);
266 }
267 // return true if the pointer was pushed
268 FORCEINLINE bool Free(void* InPtr, uint32 InPoolIndex, uint32 InBlockSize, const FArenaParams& LocalArenaParams)
269 {
270 return FreeLists[InPoolIndex].PushToFront(InPtr, InPoolIndex, InBlockSize, LocalArenaParams);
271 }
272 // return true if a pointer can be pushed
273 FORCEINLINE bool CanFree(uint32 InPoolIndex, uint32 InBlockSize, const FArenaParams& LocalArenaParams)
274 {
275 return FreeLists[InPoolIndex].CanPushToFront(InPoolIndex, InBlockSize, LocalArenaParams);
276 }
277 // returns a bundle that needs to be freed if it can't be recycled
278 FBundleNode* RecycleFullBundle(FArenaParams& LocalArenaParams, FGlobalRecycler& GlobalRecycler, uint32 InPoolIndex)
279 {
280 return FreeLists[InPoolIndex].RecyleFull(LocalArenaParams, GlobalRecycler, InPoolIndex);
281 }
282 // returns true if we have anything to pop
283 bool ObtainRecycledPartial(FArenaParams& LocalArenaParams, FGlobalRecycler& GlobalRecycler, uint32 InPoolIndex)
284 {
285 return FreeLists[InPoolIndex].ObtainPartial(LocalArenaParams, GlobalRecycler, InPoolIndex);
286 }
287 FBundleNode* PopBundles(uint32 InPoolIndex)
288 {
289 return FreeLists[InPoolIndex].PopBundles(InPoolIndex);
290 }
291 int64 AllocatedMemory;
292 TArray<FFreeBlockList> FreeLists;
293 };
294
295 struct FGlobalRecycler
296 {
297 void Init(uint32 PoolCount)
298 {
299 Bundles.AddDefaulted(PoolCount);
300 }
301 bool PushBundle(uint32 NumCachedBundles, uint32 InPoolIndex, FBundleNode* InBundle)
302 {
303 for (uint32 Slot = 0; Slot < NumCachedBundles && Slot < BINNEDARENA_MAX_GMallocBinnedArenaMaxBundlesBeforeRecycle; Slot++)
304 {
305 if (!Bundles[InPoolIndex].FreeBundles[Slot])
306 {
307 if (!FPlatformAtomics::InterlockedCompareExchangePointer((void**)&Bundles[InPoolIndex].FreeBundles[Slot], InBundle, nullptr))
308 {
309 return true;
310 }
311 }
312 }
313 return false;
314 }
315
316 FBundleNode* PopBundle(uint32 NumCachedBundles, uint32 InPoolIndex)
317 {
318 for (uint32 Slot = 0; Slot < NumCachedBundles && Slot < BINNEDARENA_MAX_GMallocBinnedArenaMaxBundlesBeforeRecycle; Slot++)
319 {
320 FBundleNode* Result = Bundles[InPoolIndex].FreeBundles[Slot];
321 if (Result)
322 {
323 if (FPlatformAtomics::InterlockedCompareExchangePointer((void**)&Bundles[InPoolIndex].FreeBundles[Slot], nullptr, Result) == Result)
324 {
325 return Result;
326 }
327 }
328 }
329 return nullptr;
330 }
331
332 private:
333 struct FPaddedBundlePointer
334 {
335 FBundleNode* FreeBundles[BINNEDARENA_MAX_GMallocBinnedArenaMaxBundlesBeforeRecycle];
336 FPaddedBundlePointer()
337 {
338 DefaultConstructItems<FBundleNode*>(FreeBundles, BINNEDARENA_MAX_GMallocBinnedArenaMaxBundlesBeforeRecycle);
339 }
340 };
341 TArray<FPaddedBundlePointer> Bundles;
342 };
343
344
345 FORCEINLINE uint64 PoolIndexFromPtr(const void* Ptr)
346 {
347 if (PoolSearchDiv == 0)
348 {
349 return (UPTRINT(Ptr) - UPTRINT(PoolBaseVMPtr[0])) >> ArenaParams.MaxMemoryPerBlockSizeShift;
350 }
351 uint64 PoolIndex = ArenaParams.PoolCount;
352 if (((uint8*)Ptr >= PoolBaseVMPtr[0]) & ((uint8*)Ptr < HighestPoolBaseVMPtr + ArenaParams.MaxMemoryPerBlockSize))
353 {
354 PoolIndex = uint64((uint8*)Ptr - PoolBaseVMPtr[0]) / PoolSearchDiv;
355 if (PoolIndex >= ArenaParams.PoolCount)
356 {
357 PoolIndex = ArenaParams.PoolCount - 1;
358 }
359 if ((uint8*)Ptr < PoolBaseVMPtr[(int32)PoolIndex])
360 {
361 do
362 {
363 PoolIndex--;
364 check(PoolIndex < ArenaParams.PoolCount);
365 } while ((uint8*)Ptr < PoolBaseVMPtr[(int32)PoolIndex]);
366 if ((uint8*)Ptr >= PoolBaseVMPtr[(int32)PoolIndex] + ArenaParams.MaxMemoryPerBlockSize)
367 {
368 PoolIndex = ArenaParams.PoolCount; // was in the gap
369 }
370 }
371 else if ((uint8*)Ptr >= PoolBaseVMPtr[(int32)PoolIndex] + ArenaParams.MaxMemoryPerBlockSize)
372 {
373 do
374 {
375 PoolIndex++;
376 check(PoolIndex < ArenaParams.PoolCount);
377 } while ((uint8*)Ptr >= PoolBaseVMPtr[(int32)PoolIndex] + ArenaParams.MaxMemoryPerBlockSize);
378 if ((uint8*)Ptr < PoolBaseVMPtr[(int32)PoolIndex])
379 {
380 PoolIndex = ArenaParams.PoolCount; // was in the gap
381 }
382 }
383 }
384 return PoolIndex;
385 }
386
387 FORCEINLINE uint8* PoolBasePtr(uint32 InPoolIndex)
388 {
389 return PoolBaseVMPtr[InPoolIndex];
390 }
391 FORCEINLINE uint64 PoolIndexFromPtrChecked(const void* Ptr)
392 {
393 uint64 Result = PoolIndexFromPtr(Ptr);
394 check(Result < ArenaParams.PoolCount);
395 return Result;
396 }
397
398 FORCEINLINE bool IsOSAllocation(const void* Ptr)
399 {
400 return PoolIndexFromPtr(Ptr) >= ArenaParams.PoolCount;
401 }
402
403
404 FORCEINLINE void* BlockOfBlocksPointerFromContainedPtr(const void* Ptr, uint8 PagesPlatformForBlockOfBlocks, uint32& OutBlockOfBlocksIndex)
405 {
406 uint32 PoolIndex = PoolIndexFromPtrChecked(Ptr);
407 uint8* PoolStart = PoolBasePtr(PoolIndex);
408 uint64 BlockOfBlocksIndex = (UPTRINT(Ptr) - UPTRINT(PoolStart)) / (UPTRINT(PagesPlatformForBlockOfBlocks) * UPTRINT(ArenaParams.AllocationGranularity));
409 OutBlockOfBlocksIndex = BlockOfBlocksIndex;
410
411 uint8* Result = PoolStart + BlockOfBlocksIndex * UPTRINT(PagesPlatformForBlockOfBlocks) * UPTRINT(ArenaParams.AllocationGranularity);
412
413 check(Result < PoolStart + ArenaParams.MaxMemoryPerBlockSize);
414 return Result;
415 }
416 FORCEINLINE uint8* BlockPointerFromIndecies(uint32 InPoolIndex, uint32 BlockOfBlocksIndex, uint32 BlockOfBlocksSize)
417 {
418 uint8* PoolStart = PoolBasePtr(InPoolIndex);
419 uint8* Ptr = PoolStart + BlockOfBlocksIndex * uint64(BlockOfBlocksSize);
420 check(Ptr + BlockOfBlocksSize <= PoolStart + ArenaParams.MaxMemoryPerBlockSize);
421 return Ptr;
422 }
423 FPoolInfoSmall* PushNewPoolToFront(FMallocBinnedArena& Allocator, uint32 InBlockSize, uint32 InPoolIndex, uint32& OutBlockOfBlocksIndex);
424 FPoolInfoSmall* GetFrontPool(FPoolTable& Table, uint32 InPoolIndex, uint32& OutBlockOfBlocksIndex);
425
426 FORCEINLINE bool AdjustSmallBlockSizeForAlignment(SIZE_T& InOutSize, uint32 Alignment)
427 {
428 if ((InOutSize <= ArenaParams.MaxPoolSize) & (Alignment <= ArenaParams.MinimumAlignment)) // one branch, not two
429 {
430 return true;
431 }
432 SIZE_T AlignedSize = Align(InOutSize, Alignment);
433 if (ArenaParams.bAttemptToAlignSmallBocks & (AlignedSize <= ArenaParams.MaxPoolSize) & (Alignment <= ArenaParams.MaximumAlignmentForSmallBlock)) // one branch, not three
434 {
435 uint32 PoolIndex = BoundSizeToPoolIndex(AlignedSize);
436 while (true)
437 {
438 uint32 BlockSize = PoolIndexToBlockSize(PoolIndex);
439 if (IsAligned(BlockSize, Alignment))
440 {
441 InOutSize = SIZE_T(BlockSize);
442 return true;
443 }
444 PoolIndex++;
445 check(PoolIndex < ArenaParams.PoolCount);
446 }
447 }
448 return false;
449 }
450
451public:
452
453
454 FMallocBinnedArena();
455 FArenaParams& GetParams()
456 {
457 return ArenaParams;
458 }
459 void InitMallocBinned();
460
461 virtual ~FMallocBinnedArena();
462
463
464 // FMalloc interface.
465 virtual bool IsInternallyThreadSafe() const override;
466 FORCEINLINE virtual void* Malloc(SIZE_T Size, uint32 Alignment) override
467 {
468 Alignment = FMath::Max<uint32>(Alignment, ArenaParams.MinimumAlignment);
469
470 void* Result = nullptr;
471
472 // Only allocate from the small pools if the size is small enough and the alignment isn't crazy large.
473 // With large alignments, we'll waste a lot of memory allocating an entire page, but such alignments are highly unlikely in practice.
474 if (AdjustSmallBlockSizeForAlignment(Size, Alignment))
475 {
476 FPerThreadFreeBlockLists* Lists = ArenaParams.bPerThreadCaches ? FPerThreadFreeBlockLists::Get(BinnedArenaTlsSlot) : nullptr;
477 if (Lists)
478 {
479 uint32 PoolIndex = BoundSizeToPoolIndex(Size);
480 uint32 BlockSize = PoolIndexToBlockSize(PoolIndex);
481 Result = Lists->Malloc(PoolIndex);
482 if (Result)
483 {
484 Lists->AllocatedMemory += BlockSize;
485 checkSlow(IsAligned(Result, Alignment));
486 }
487 }
488 }
489 if (Result == nullptr)
490 {
491 Result = MallocExternal(Size, Alignment);
492 }
493
494 return Result;
495 }
496 FORCEINLINE virtual void* Realloc(void* Ptr, SIZE_T NewSize, uint32 Alignment) override
497 {
498 Alignment = FMath::Max<uint32>(Alignment, ArenaParams.MinimumAlignment);
499 if (AdjustSmallBlockSizeForAlignment(NewSize, Alignment))
500 {
501 FPerThreadFreeBlockLists* Lists = ArenaParams.bPerThreadCaches ? FPerThreadFreeBlockLists::Get(BinnedArenaTlsSlot) : nullptr;
502
503 uint64 PoolIndex = PoolIndexFromPtr(Ptr);
504 if ((!!Lists) & ((!Ptr) | (PoolIndex < ArenaParams.PoolCount)))
505 {
506 uint32 BlockSize = 0;
507
508 bool bCanFree = true; // the nullptr is always "freeable"
509 if (Ptr)
510 {
511 // Reallocate to a smaller/bigger pool if necessary
512 BlockSize = PoolIndexToBlockSize(PoolIndex);
513 if ((!!NewSize) & (NewSize <= BlockSize) & IsAligned(BlockSize, Alignment) & ((!PoolIndex) | (NewSize > PoolIndexToBlockSize(PoolIndex - 1)))) //-V792
514 {
515 return Ptr;
516 }
517 bCanFree = Lists->CanFree(PoolIndex, BlockSize, ArenaParams);
518 }
519 if (bCanFree)
520 {
521 uint32 NewPoolIndex = BoundSizeToPoolIndex(NewSize);
522 uint32 NewBlockSize = PoolIndexToBlockSize(NewPoolIndex);
523 void* Result = NewSize ? Lists->Malloc(NewPoolIndex) : nullptr;
524 if (Result)
525 {
526 Lists->AllocatedMemory += NewBlockSize;
527 }
528 if (Result || !NewSize)
529 {
530 if (Result && Ptr)
531 {
532 FMemory::Memcpy(Result, Ptr, FPlatformMath::Min<SIZE_T>(NewSize, BlockSize));
533 }
534 if (Ptr)
535 {
536 bool bDidPush = Lists->Free(Ptr, PoolIndex, BlockSize, ArenaParams);
537 checkSlow(bDidPush);
538 Lists->AllocatedMemory -= BlockSize;
539 }
540
541 return Result;
542 }
543 }
544 }
545 }
546 void* Result = ReallocExternal(Ptr, NewSize, Alignment);
547 return Result;
548 }
549
550 FORCEINLINE virtual void Free(void* Ptr) override
551 {
552 uint64 PoolIndex = PoolIndexFromPtr(Ptr);
553 if (PoolIndex < ArenaParams.PoolCount)
554 {
555 FPerThreadFreeBlockLists* Lists = ArenaParams.bPerThreadCaches ? FPerThreadFreeBlockLists::Get(BinnedArenaTlsSlot) : nullptr;
556 if (Lists)
557 {
558 int32 BlockSize = PoolIndexToBlockSize(PoolIndex);
559 if (Lists->Free(Ptr, PoolIndex, BlockSize, ArenaParams))
560 {
561 Lists->AllocatedMemory -= BlockSize;
562 return;
563 }
564 }
565 }
566 FreeExternal(Ptr);
567 }
568 FORCEINLINE virtual bool GetAllocationSize(void *Ptr, SIZE_T &SizeOut) override
569 {
570 uint64 PoolIndex = PoolIndexFromPtr(Ptr);
571 if (PoolIndex < ArenaParams.PoolCount)
572 {
573 SizeOut = PoolIndexToBlockSize(PoolIndex);
574 return true;
575 }
576 return GetAllocationSizeExternal(Ptr, SizeOut);
577 }
578
579 FORCEINLINE virtual SIZE_T QuantizeSize(SIZE_T Count, uint32 Alignment) override
580 {
581 check(DEFAULT_ALIGNMENT <= ArenaParams.MinimumAlignment); // used below
582 checkSlow((Alignment & (Alignment - 1)) == 0); // Check the alignment is a power of two
583 SIZE_T SizeOut;
584 if ((Count <= ArenaParams.MaxPoolSize) & (Alignment <= ArenaParams.MinimumAlignment)) // one branch, not two
585 {
586 SizeOut = PoolIndexToBlockSize(BoundSizeToPoolIndex(Count));
587 }
588 else
589 {
590 Alignment = FPlatformMath::Max<uint32>(Alignment, ArenaParams.AllocationGranularity);
591 SizeOut = Align(Count, Alignment);
592 }
593 check(SizeOut >= Count);
594 return SizeOut;
595 }
596
597 virtual bool ValidateHeap() override;
598 virtual void Trim(bool bTrimThreadCaches) override;
599 virtual void SetupTLSCachesOnCurrentThread() override;
600 virtual void ClearAndDisableTLSCachesOnCurrentThread() override;
601 virtual const TCHAR* GetDescriptiveName() override;
602 // End FMalloc interface.
603
604 void FlushCurrentThreadCache();
605 void* MallocExternal(SIZE_T Size, uint32 Alignment);
606 void* ReallocExternal(void* Ptr, SIZE_T NewSize, uint32 Alignment);
607 void FreeExternal(void *Ptr);
608 bool GetAllocationSizeExternal(void* Ptr, SIZE_T& SizeOut);
609
610 MBA_STAT(int64 GetTotalAllocatedSmallPoolMemory();)
611 virtual void GetAllocatorStats(FGenericMemoryStats& out_Stats) override;
612 /** Dumps current allocator stats to the log. */
613 virtual void DumpAllocatorStats(class FOutputDevice& Ar) override;
614
615 FORCEINLINE uint32 BoundSizeToPoolIndex(SIZE_T Size)
616 {
617 auto Index = ((Size + ArenaParams.MinimumAlignment - 1) >> ArenaParams.MinimumAlignmentShift);
618 checkSlow(Index >= 0 && Index <= (ArenaParams.MaxPoolSize >> ArenaParams.MinimumAlignmentShift)); // and it should be in the table
619 uint32 PoolIndex = uint32(MemSizeToIndex[Index]);
620 checkSlow(PoolIndex >= 0 && PoolIndex < ArenaParams.PoolCount);
621 return PoolIndex;
622 }
623 FORCEINLINE uint32 PoolIndexToBlockSize(uint32 PoolIndex)
624 {
625 return uint32(SmallBlockSizesReversedShifted[ArenaParams.PoolCount - PoolIndex - 1]) << ArenaParams.MinimumAlignmentShift;
626 }
627
628 void Commit(uint32 InPoolIndex, void *Ptr, SIZE_T Size);
629 void Decommit(uint32 InPoolIndex, void *Ptr, SIZE_T Size);
630
631
632 // Pool tables for different pool sizes
633 TArray<FPoolTable> SmallPoolTables;
634
635 uint32 SmallPoolInfosPerPlatformPage;
636
637 PoolHashBucket* HashBuckets;
638 PoolHashBucket* HashBucketFreeList;
639 uint64 NumLargePoolsPerPage;
640
641 FCriticalSection Mutex;
642 FGlobalRecycler GGlobalRecycler;
643 FPtrToPoolMapping PtrToPoolMapping;
644
645 FArenaParams ArenaParams;
646
647 TArray<uint16> SmallBlockSizesReversedShifted; // this is reversed to get the smallest elements on our main cache line
648 uint32 BinnedArenaTlsSlot;
649 uint64 PoolSearchDiv; // if this is zero, the VM turned out to be contiguous anyway so we use a simple subtract and shift
650 uint8* HighestPoolBaseVMPtr; // this is a duplicate of PoolBaseVMPtr[ArenaParams.PoolCount - 1]
651 FPlatformMemory::FPlatformVirtualMemoryBlock PoolBaseVMBlock;
652 TArray<uint8*> PoolBaseVMPtr;
653 TArray<FPlatformMemory::FPlatformVirtualMemoryBlock> PoolBaseVMBlocks;
654 // Mapping of sizes to small table indices
655 TArray<uint8> MemSizeToIndex;
656
657 MBA_STAT(
658 int64 BinnedArenaAllocatedSmallPoolMemory = 0; // memory that's requested to be allocated by the game
659 int64 BinnedArenaAllocatedOSSmallPoolMemory = 0;
660
661 int64 BinnedArenaAllocatedLargePoolMemory = 0; // memory requests to the OS which don't fit in the small pool
662 int64 BinnedArenaAllocatedLargePoolMemoryWAlignment = 0; // when we allocate at OS level we need to align to a size
663
664 int64 BinnedArenaPoolInfoMemory = 0;
665 int64 BinnedArenaHashMemory = 0;
666 int64 BinnedArenaFreeBitsMemory = 0;
667 int64 BinnedArenaTLSMemory = 0;
668 TAtomic<int64> ConsolidatedMemory;
669 )
670
671 FCriticalSection FreeBlockListsRegistrationMutex;
672 FCriticalSection& GetFreeBlockListsRegistrationMutex()
673 {
674 return FreeBlockListsRegistrationMutex;
675 }
676 TArray<FPerThreadFreeBlockLists*> RegisteredFreeBlockLists;
677 TArray<FPerThreadFreeBlockLists*>& GetRegisteredFreeBlockLists()
678 {
679 return RegisteredFreeBlockLists;
680 }
681 void RegisterThreadFreeBlockLists(FPerThreadFreeBlockLists* FreeBlockLists)
682 {
683 FScopeLock Lock(&GetFreeBlockListsRegistrationMutex());
684 GetRegisteredFreeBlockLists().Add(FreeBlockLists);
685 }
686 int64 UnregisterThreadFreeBlockLists(FPerThreadFreeBlockLists* FreeBlockLists)
687 {
688 FScopeLock Lock(&GetFreeBlockListsRegistrationMutex());
689 int64 Result = FreeBlockLists->AllocatedMemory;
690 check(Result >= 0);
691 GetRegisteredFreeBlockLists().Remove(FreeBlockLists);
692 return Result;
693 }
694
695 TArray<void*> MallocedPointers;
696};
697
698PRAGMA_RESTORE_UNSAFE_TYPECAST_WARNINGS
699
700#endif
#define checkSlow(expr)
#define check(expr)
#define UE_BUILD_SHIPPING
Definition Build.h:4
#define MAX_uint16
#define MAX_uint32
#define MAX_uint8
#define PLATFORM_HAS_FPlatformVirtualMemoryBlock
Definition Platform.h:537
#define FORCEINLINE
Definition Platform.h:644
#define PLATFORM_64BITS