Ark Server API (ASA) - Wiki
Loading...
Searching...
No Matches
MallocBinned2.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#include "HAL/Allocators/CachedOSPageAllocator.h"
7#include "HAL/Allocators/CachedOSVeryLargePageAllocator.h"
8#include "HAL/Allocators/PooledVirtualMemoryAllocator.h"
9#include "HAL/CriticalSection.h"
10#include "HAL/LowLevelMemTracker.h"
11#include "HAL/MemoryBase.h"
12#include "HAL/PlatformMath.h"
13#include "HAL/PlatformTLS.h"
14#include "HAL/UnrealMemory.h"
15#include "Math/NumericLimits.h"
16#include "Misc/AssertionMacros.h"
17#include "Misc/Fork.h"
18#include "Templates/AlignmentTemplates.h"
19#include "Templates/Atomic.h"
20
22
23#define BINNED2_MAX_CACHED_OS_FREES (64)
25 #define BINNED2_MAX_CACHED_OS_FREES_BYTE_LIMIT (64*1024*1024)
26#else
27 #define BINNED2_MAX_CACHED_OS_FREES_BYTE_LIMIT (16*1024*1024)
28#endif
29
30#define BINNED2_LARGE_ALLOC 65536 // Alignment of OS-allocated pointer - pool-allocated pointers will have a non-aligned pointer
31#define BINNED2_MINIMUM_ALIGNMENT_SHIFT 4 // Alignment of blocks, expressed as a shift
32#define BINNED2_MINIMUM_ALIGNMENT 16 // Alignment of blocks
33#define BINNED2_MAX_SMALL_POOL_SIZE (32768-16) // Maximum block size in GMallocBinned2SmallBlockSizes
34#define BINNED2_SMALL_POOL_COUNT 45
35
36
37#define DEFAULT_GMallocBinned2PerThreadCaches 1
38#define DEFAULT_GMallocBinned2LockFreeCaches 0
39#define DEFAULT_GMallocBinned2BundleCount 64
40#define DEFAULT_GMallocBinned2AllocExtra 32
41#define BINNED2_MAX_GMallocBinned2MaxBundlesBeforeRecycle 8
42#define DEFAULT_GMallocBinned2MoveOSFreesOffTimeCriticalThreads 1
43
44#if !defined(AGGRESSIVE_MEMORY_SAVING)
45 #error "AGGRESSIVE_MEMORY_SAVING must be defined"
46#endif
48 #define DEFAULT_GMallocBinned2BundleSize 8192
49#else
50 #define DEFAULT_GMallocBinned2BundleSize BINNED2_LARGE_ALLOC
51#endif
52
53// When book keeping is at the end of FFreeBlock, MallocBinned2 cannot tell if the allocation comes from a large allocation (higher than 64KB, also named as "OSAllocation")
54// or from VeryLargePageAllocator that fell back to FCachedOSPageAllocator. In both cases the allocation (large or small) might be aligned to 64KB.
55// bookKeeping at the end needs to be disabled if we want VeryLargePageAllocator to properly fallback to regular TCachedOSPageAllocator if needed
56#ifndef BINNED2_BOOKKEEPING_AT_THE_END_OF_LARGEBLOCK
57#define BINNED2_BOOKKEEPING_AT_THE_END_OF_LARGEBLOCK 0
58#endif
59
60// If we are emulating forking on a windows server or are a linux server, enable support for avoiding dirtying pages owned by the parent.
61#ifndef BINNED2_FORK_SUPPORT
62 #define BINNED2_FORK_SUPPORT (UE_SERVER && (PLATFORM_UNIX || DEFAULT_SERVER_FAKE_FORKS))
63#endif
64
65
66#define BINNED2_ALLOW_RUNTIME_TWEAKING 0
68 extern int32 GMallocBinned2PerThreadCaches;
69 extern int32 GMallocBinned2BundleSize = DEFAULT_GMallocBinned2BundleSize;
70 extern int32 GMallocBinned2BundleCount = DEFAULT_GMallocBinned2BundleCount;
71 extern int32 GMallocBinned2MaxBundlesBeforeRecycle = BINNED2_MAX_GMallocBinned2MaxBundlesBeforeRecycle;
72 extern int32 GMallocBinned2AllocExtra = DEFAULT_GMallocBinned2AllocExtra;
73 extern int32 GMallocBinned2MoveOSFreesOffTimeCriticalThreads = DEFAULT_GMallocBinned2MoveOSFreesOffTimeCriticalThreads;
74
75#else
76 #define GMallocBinned2PerThreadCaches DEFAULT_GMallocBinned2PerThreadCaches
77 #define GMallocBinned2BundleSize DEFAULT_GMallocBinned2BundleSize
78 #define GMallocBinned2BundleCount DEFAULT_GMallocBinned2BundleCount
79 #define GMallocBinned2MaxBundlesBeforeRecycle BINNED2_MAX_GMallocBinned2MaxBundlesBeforeRecycle
80 #define GMallocBinned2AllocExtra DEFAULT_GMallocBinned2AllocExtra
81 #define GMallocBinned2MoveOSFreesOffTimeCriticalThreads DEFAULT_GMallocBinned2MoveOSFreesOffTimeCriticalThreads
82#endif
83
84
85#ifndef BINNED2_ALLOCATOR_STATS
87 #define BINNED2_ALLOCATOR_STATS 0
88 #else
89 #define BINNED2_ALLOCATOR_STATS 1
90 #endif
91#endif
92
93
94#define BINNED2_ALLOCATOR_STATS_VALIDATION (BINNED2_ALLOCATOR_STATS && 0)
95
97//////////////////////////////////////////////////////////////////////////
98// the following don't need a critical section because they are covered by the critical section called Mutex
99extern TAtomic<int64> AllocatedSmallPoolMemory; // memory that's requested to be allocated by the game
100extern TAtomic<int64> AllocatedOSSmallPoolMemory;
101extern TAtomic<int64> AllocatedLargePoolMemory; // memory requests to the OS which don't fit in the small pool
102extern TAtomic<int64> AllocatedLargePoolMemoryWAlignment; // when we allocate at OS level we need to align to a size
103#endif
105#include "Misc/ScopeLock.h"
106
107extern int64 AllocatedSmallPoolMemoryValidation;
108extern FCriticalSection ValidationCriticalSection;
109extern int32 RecursionCounter;
110#endif
111
112// Canary value used in FFreeBlock
113// A constant value unless we're compiled with fork support in which case there are two values identifying whether the page
114// was allocated pre- or post-fork
115enum class EBlockCanary : uint8
116{
117 Zero = 0x0, // Not clear why this is needed by FreeBundles
119 PreFork = 0xb7,
120 PostFork = 0xca,
121#else
122 Value = 0xe3
123#endif
124};
125
126
127//
128// Optimized virtual memory allocator.
129//
131{
132 // Forward declares.
133 struct FPoolInfo;
134 struct PoolHashBucket;
135 struct Private;
136
137 /** Information about a piece of free memory. */
139 {
140 FORCEINLINE FFreeBlock(uint32 InPageSize, uint16 InBlockSize, uint8 InPoolIndex, EBlockCanary InCanary)
141 : BlockSize(InBlockSize)
142 , PoolIndex(InPoolIndex)
143 , CanaryAndForkState(InCanary)
144 , NextFreeBlock(nullptr)
145 {
146 check(InPoolIndex < MAX_uint8 && InBlockSize <= MAX_uint16);
147 NumFreeBlocks = InPageSize / InBlockSize;
148 if (NumFreeBlocks * InBlockSize + sizeof(FFreeBlock) > InPageSize)
149 {
151 }
152 check(NumFreeBlocks * InBlockSize + sizeof(FFreeBlock) <= InPageSize);
153 }
154
156 {
157 return NumFreeBlocks;
158 }
159
160
162 {
165 if (IsAligned(this, BINNED2_LARGE_ALLOC))
166 {
167 return (uint8*)this + BINNED2_LARGE_ALLOC - (NumFreeBlocks + 1) * BlockSize;
168 }
169#else
170 if (IsAligned(((uintptr_t)this)+sizeof(FFreeBlock), BINNED2_LARGE_ALLOC))
171 {
172 // The book keeping FreeBlock is at the end of the "page" so we align down to get to the beginning of the page
173 uintptr_t ptr = AlignDown((uintptr_t)this, BINNED2_LARGE_ALLOC);
174 // And we offset the returned pointer based on how many free blocks are left.
175 return (uint8*) ptr + (NumFreeBlocks * BlockSize);
176 }
177#endif
178 return (uint8*)this + (NumFreeBlocks)* BlockSize;
179 }
180
181 uint16 BlockSize; // Size of the blocks that this list points to
182 uint8 PoolIndex; // Index of this pool
183
184 // Normally this value just functions as a canary to detect invalid memory state.
185 // When process forking is supported, it's still a canary but it has two valid values.
186 // One value is used pre-fork and one post-fork and the value is used to avoid freeing memory in pages shared with the parent process.
188
189 uint32 NumFreeBlocks; // Number of consecutive free blocks here, at least 1.
190 void* NextFreeBlock; // Next free block in another pool
191 };
192
194 {
196
197 void Clear();
198 bool IsEmpty() const;
199
200 FPoolInfo& GetFrontPool();
201 const FPoolInfo& GetFrontPool() const;
202
203 void LinkToFront(FPoolInfo* Pool);
204
205 FPoolInfo& PushNewPoolToFront(FMallocBinned2& Allocator, uint32 InBytes, uint32 InPoolIndex);
206
209
210 private:
211 FPoolInfo* Front;
212 };
213
214 /** Pool table. */
216 {
219 uint32 BlockSize;
220
222 };
223
225 {
228 , HashKeyShift(0)
229 , PoolMask(0)
230 , MaxHashBuckets(0)
231 {
232 }
233 explicit FPtrToPoolMapping(uint32 InPageSize, uint64 InNumPoolsPerPage, uint64 AddressLimit)
234 {
235 Init(InPageSize, InNumPoolsPerPage, AddressLimit);
236 }
237
238 void Init(uint32 InPageSize, uint64 InNumPoolsPerPage, uint64 AddressLimit)
239 {
240 uint64 PoolPageToPoolBitShift = FPlatformMath::CeilLogTwo64(InNumPoolsPerPage);
241
242 PtrToPoolPageBitShift = FPlatformMath::CeilLogTwo(InPageSize);
243 HashKeyShift = PtrToPoolPageBitShift + PoolPageToPoolBitShift;
244 PoolMask = (1ull << PoolPageToPoolBitShift) - 1;
245 MaxHashBuckets = AddressLimit >> HashKeyShift;
246 }
247
248 FORCEINLINE void GetHashBucketAndPoolIndices(const void* InPtr, uint32& OutBucketIndex, UPTRINT& OutBucketCollision, uint32& OutPoolIndex) const
249 {
250 OutBucketCollision = (UPTRINT)InPtr >> HashKeyShift;
251 OutBucketIndex = uint32(OutBucketCollision & (MaxHashBuckets - 1));
252 OutPoolIndex = (uint32)(((UPTRINT)InPtr >> PtrToPoolPageBitShift) & PoolMask);
253 }
254
256 {
257 return MaxHashBuckets;
258 }
259
260 private:
261 /** Shift to apply to a pointer to get the reference from the indirect tables */
263
264 /** Shift required to get required hash table key. */
266
267 /** Used to mask off the bits that have been used to lookup the indirect table */
268 uint64 PoolMask;
269
270 // PageSize dependent constants
272 };
273
275
276 // Pool tables for different pool sizes
278
279 PoolHashBucket* HashBuckets;
280 PoolHashBucket* HashBucketFreeList;
283 EBlockCanary CurrentCanary = EBlockCanary::PreFork; // The value of the canary for pages we have allocated this side of the fork
284 EBlockCanary OldCanary = EBlockCanary::PreFork; // If we have forked, the value canary of old pages we should avoid touching
285#else
287#endif
288
292#else
294#endif
295#else
297#endif
298
300
301 FORCEINLINE bool IsOSAllocation(const void* Ptr)
302 {
304 return !CachedOSPageAllocator.IsPartOf(Ptr) && IsAligned(Ptr, BINNED2_LARGE_ALLOC);
305#else
306 return IsAligned(Ptr, BINNED2_LARGE_ALLOC);
307#endif
308 }
309
310 // This needs to be small enough to fit inside the smallest allocation handled by MallocBinned2, hence the union.
312 {
314
315 // NextBundle ptr is valid when node is stored in FFreeBlockList in a thread-local list of reusable allocations.
316 // Count is valid when node is stored in global recycler and caches the number of nodes in the list formed by NextNodeInCurrentBundle.
317 union
318 {
320 int32 Count;
321 };
322 };
323
324 struct FBundle
325 {
327 {
328 Reset();
329 }
330
332 {
333 Head = nullptr;
334 Count = 0;
335 }
336
338 {
340 Node->NextBundle = nullptr;
341 Head = Node;
342 Count++;
343 }
344
346 {
347 FBundleNode* Result = Head;
348
349 Count--;
351 return Result;
352 }
353
355 uint32 Count;
356 };
357
359 {
360 // return true if we actually pushed it
361 FORCEINLINE bool PushToFront(void* InPtr, uint32 InPoolIndex, uint32 InBlockSize)
362 {
363 checkSlow(InPtr);
364
366 {
367 if (FullBundle.Head)
368 {
369 return false;
370 }
373 }
375 return true;
376 }
377 FORCEINLINE bool CanPushToFront(uint32 InPoolIndex, uint32 InBlockSize)
378 {
380 {
381 return false;
382 }
383 return true;
384 }
385 FORCEINLINE void* PopFromFront(uint32 InPoolIndex)
386 {
388 {
389 if (FullBundle.Head)
390 {
393 }
394 }
396 }
397
398 // tries to recycle the full bundle, if that fails, it is returned for freeing
399 FBundleNode* RecyleFull(uint32 InPoolIndex);
400 bool ObtainPartial(uint32 InPoolIndex);
401 FBundleNode* PopBundles(uint32 InPoolIndex);
402 private:
405 };
406
408 {
410 {
412 }
413 static void SetTLS();
414 static void ClearTLS();
415
418 : AllocatedMemory(0)
419#endif
420 {
421 }
422
423 FORCEINLINE void* Malloc(uint32 InPoolIndex)
424 {
425 return FreeLists[InPoolIndex].PopFromFront(InPoolIndex);
426 }
427 // return true if the pointer was pushed
428 FORCEINLINE bool Free(void* InPtr, uint32 InPoolIndex, uint32 InBlockSize)
429 {
430 return FreeLists[InPoolIndex].PushToFront(InPtr, InPoolIndex, InBlockSize);
431 }
432 // return true if a pointer can be pushed
433 FORCEINLINE bool CanFree(uint32 InPoolIndex, uint32 InBlockSize)
434 {
435 return FreeLists[InPoolIndex].CanPushToFront(InPoolIndex, InBlockSize);
436 }
437 // returns a bundle that needs to be freed if it can't be recycled
438 FBundleNode* RecycleFullBundle(uint32 InPoolIndex)
439 {
440 return FreeLists[InPoolIndex].RecyleFull(InPoolIndex);
441 }
442 // returns true if we have anything to pop
443 bool ObtainRecycledPartial(uint32 InPoolIndex)
444 {
445 return FreeLists[InPoolIndex].ObtainPartial(InPoolIndex);
446 }
447 FBundleNode* PopBundles(uint32 InPoolIndex)
448 {
449 return FreeLists[InPoolIndex].PopBundles(InPoolIndex);
450 }
452 public:
455#endif
456 private:
458 };
459
461 {
463 return (FFreeBlock*)AlignDown(Ptr, BINNED2_LARGE_ALLOC);
464#else
465 return (FFreeBlock*) (AlignDown((uintptr_t) Ptr, BINNED2_LARGE_ALLOC) + BINNED2_LARGE_ALLOC - sizeof(FFreeBlock));
466#endif
467 }
468
469public:
470
471
473
474 virtual ~FMallocBinned2();
475
476 // FMalloc interface.
477 virtual bool IsInternallyThreadSafe() const override;
478 FORCEINLINE virtual void* Malloc(SIZE_T Size, uint32 Alignment) override
479 {
481 FScopeLock Lock(&ValidationCriticalSection);
482 ++RecursionCounter;
483 void *Result = MallocInline(Size, Alignment);
484 if ( !IsOSAllocation(Result))
485 {
486 SIZE_T OutSize;
487 ensure( GetAllocationSize(Result, OutSize) );
488 AllocatedSmallPoolMemoryValidation += OutSize;
489 if (RecursionCounter==1)
490 {
491 check(GetTotalAllocatedSmallPoolMemory() == AllocatedSmallPoolMemoryValidation);
492 if (GetTotalAllocatedSmallPoolMemory() != AllocatedSmallPoolMemoryValidation)
493 {
494 UE_DEBUG_BREAK();
495 }
496 }
497 }
498 --RecursionCounter;
499 return Result;
500#else
501 return MallocInline(Size, Alignment);
502#endif
503 }
504 FORCEINLINE void* MallocInline(SIZE_T Size, uint32 Alignment )
505 {
506
508
509 if (Alignment > BINNED2_MINIMUM_ALIGNMENT && (Size <= BINNED2_MAX_SMALL_POOL_SIZE))
510 {
511 Size = Align(Size, Alignment);
512 }
513#endif
514 void* Result = nullptr;
515 // Only allocate from the small pools if the size is small enough and the alignment isn't crazy large.
516 // With large alignments, we'll waste a lot of memory allocating an entire page, but such alignments are highly unlikely in practice.
518 if ((Size <= BINNED2_MAX_SMALL_POOL_SIZE)) // one branch, not two
519#else
520 if ((Size <= BINNED2_MAX_SMALL_POOL_SIZE) & (Alignment <= BINNED2_MINIMUM_ALIGNMENT)) // one branch, not two
521#endif
522 {
524 if (Lists)
525 {
526 uint32 PoolIndex = BoundSizeToPoolIndex(Size);
527 uint32 BlockSize = PoolIndexToBlockSize(PoolIndex);
528 Result = Lists->Malloc(PoolIndex);
530 if (Result)
531 {
532 Lists->AllocatedMemory += BlockSize;
533 }
534#endif
535 }
536 }
537 if (Result == nullptr)
538 {
539 Result = MallocSelect(Size, Alignment);
540 }
541
542 return Result;
543 }
544 FORCEINLINE static bool UseSmallAlloc(SIZE_T Size, uint32 Alignment)
545 {
547 if (Alignment > BINNED2_MINIMUM_ALIGNMENT)
548 {
549 Size = Align(Size, Alignment);
550 }
551 bool bResult = (Size <= BINNED2_MAX_SMALL_POOL_SIZE);
552#else
553 bool bResult = ((Size <= BINNED2_MAX_SMALL_POOL_SIZE) & (Alignment <= BINNED2_MINIMUM_ALIGNMENT)); // one branch, not two
554#endif
555 return bResult;
556 }
557 FORCEINLINE void* MallocSelect(SIZE_T Size, uint32 Alignment)
558 {
559 void* Result;
560
561 if (UseSmallAlloc(Size, Alignment))
562 {
563 Result = MallocExternalSmall(Size, Alignment);
564 }
565 else
566 {
567 Result = MallocExternalLarge(Size, Alignment);
568 }
569
570 return Result;
571 }
572 FORCEINLINE virtual void* Realloc(void* Ptr, SIZE_T NewSize, uint32 Alignment) override
573 {
575 bool bOldIsOsAllocation = IsOSAllocation(Ptr);
576 SIZE_T OldSize;
577 if (!bOldIsOsAllocation)
578 {
579 ensure(GetAllocationSize(Ptr, OldSize));
580 }
581 FScopeLock Lock(&ValidationCriticalSection);
582 ++RecursionCounter;
583 void *Result = ReallocInline(Ptr, NewSize, Alignment);
584 if ( !bOldIsOsAllocation )
585 {
586 AllocatedSmallPoolMemoryValidation -= OldSize;
587 }
588 if (!IsOSAllocation(Result))
589 {
590 SIZE_T OutSize;
591 ensure(GetAllocationSize(Result, OutSize));
592 AllocatedSmallPoolMemoryValidation += OutSize;
593 }
594 if (RecursionCounter == 1)
595 {
596 check(GetTotalAllocatedSmallPoolMemory() == AllocatedSmallPoolMemoryValidation);
597 if (GetTotalAllocatedSmallPoolMemory() != AllocatedSmallPoolMemoryValidation)
598 {
599 UE_DEBUG_BREAK();
600 }
601 }
602 --RecursionCounter;
603 return Result;
604#else
605 return ReallocInline(Ptr, NewSize, Alignment);
606#endif
607 }
608 FORCEINLINE void* ReallocInline(void* Ptr, SIZE_T NewSize, uint32 Alignment)
609 {
611 if (Alignment > BINNED2_MINIMUM_ALIGNMENT && (NewSize <= BINNED2_MAX_SMALL_POOL_SIZE))
612 {
613 NewSize = Align(NewSize, Alignment);
614 }
615 if (NewSize <= BINNED2_MAX_SMALL_POOL_SIZE)
616#else
617if (NewSize <= BINNED2_MAX_SMALL_POOL_SIZE && Alignment <= BINNED2_MINIMUM_ALIGNMENT) // one branch, not two
618#endif
619 {
621 if (Lists && (!Ptr || !IsOSAllocation(Ptr)))
622 {
623 uint32 BlockSize = 0;
624 uint32 PoolIndex = 0;
625
626 bool bCanFree = true; // the nullptr is always "freeable"
627 if (Ptr)
628 {
629 // Reallocate to a smaller/bigger pool if necessary
631 BlockSize = Free->BlockSize;
632 PoolIndex = Free->PoolIndex;
633 // If canary is invalid we will assert in ReallocExternal. Otherwise it's the pre-fork canary and we will allocate new memory without touching this allocation.
634 bCanFree = Free->CanaryAndForkState == CurrentCanary;
635 if (NewSize && bCanFree && NewSize <= BlockSize && (PoolIndex == 0 || NewSize > PoolIndexToBlockSize(PoolIndex - 1)))
636 {
637 return Ptr;
638 }
639 bCanFree = bCanFree && Lists->CanFree(PoolIndex, BlockSize);
640 }
641 if (bCanFree)
642 {
643 uint32 NewPoolIndex = BoundSizeToPoolIndex(NewSize);
644 uint32 NewBlockSize = PoolIndexToBlockSize(NewPoolIndex);
645 void* Result = NewSize ? Lists->Malloc(NewPoolIndex) : nullptr;
647 if (Result)
648 {
649 Lists->AllocatedMemory += NewBlockSize;
650 }
651#endif
652 if (Result || !NewSize)
653 {
654 if (Result && Ptr)
655 {
656 FMemory::Memcpy(Result, Ptr, FPlatformMath::Min<SIZE_T>(NewSize, BlockSize));
657 }
658 if (Ptr)
659 {
660 bool bDidPush = Lists->Free(Ptr, PoolIndex, BlockSize);
661 checkSlow(bDidPush);
663 Lists->AllocatedMemory -= BlockSize;
664#endif
665 }
666
667 return Result;
668 }
669 }
670 }
671 }
672 void* Result = ReallocExternal(Ptr, NewSize, Alignment);
673 return Result;
674 }
675
676 FORCEINLINE virtual void Free(void* Ptr) override
677 {
679 FScopeLock Lock(&ValidationCriticalSection);
680 ++RecursionCounter;
681 if (!IsOSAllocation(Ptr))
682 {
683 SIZE_T OutSize;
684 ensure(GetAllocationSize(Ptr, OutSize));
685 AllocatedSmallPoolMemoryValidation -= OutSize;
686 }
687 FreeInline(Ptr);
688 if (RecursionCounter == 1)
689 {
690 check(GetTotalAllocatedSmallPoolMemory() == AllocatedSmallPoolMemoryValidation);
691 if (GetTotalAllocatedSmallPoolMemory() != AllocatedSmallPoolMemoryValidation)
692 {
693 UE_DEBUG_BREAK();
694 }
695 }
696 --RecursionCounter;
697#else
698 FreeInline(Ptr);
699#endif
700 }
701
702 FORCEINLINE void FreeInline(void* Ptr)
703 {
704 if (!IsOSAllocation(Ptr))
705 {
707 if (Lists)
708 {
710 int32 BlockSize = BasePtr->BlockSize;
711 // If canary is invalid we will assert in FreeExternal. Otherwise it's the pre-fork canary and we will turn this free into a no-op.
712 if (BasePtr->CanaryAndForkState == CurrentCanary && Lists->Free(Ptr, BasePtr->PoolIndex, BasePtr->BlockSize))
713 {
715 Lists->AllocatedMemory -= BasePtr->BlockSize;
716#endif
717 return;
718 }
719 }
720 }
722 }
723 FORCEINLINE virtual bool GetAllocationSize(void *Ptr, SIZE_T &SizeOut) override
724 {
725 if (!IsOSAllocation(Ptr))
726 {
727 const FFreeBlock* Free = GetPoolHeaderFromPointer(Ptr);
729 if (Free->CanaryAndForkState == CurrentCanary || Free->CanaryAndForkState == OldCanary)
730#else
732#endif
733 {
734 SizeOut = Free->BlockSize;
735 return true;
736 }
737 }
738 return GetAllocationSizeExternal(Ptr, SizeOut);
739 }
740
741 FORCEINLINE virtual SIZE_T QuantizeSize(SIZE_T Count, uint32 Alignment) override
742 {
743 static_assert(DEFAULT_ALIGNMENT <= BINNED2_MINIMUM_ALIGNMENT, "DEFAULT_ALIGNMENT is assumed to be zero"); // used below
744 checkSlow((Alignment & (Alignment - 1)) == 0); // Check the alignment is a power of two
745 SIZE_T SizeOut;
746 if ((Count <= BINNED2_MAX_SMALL_POOL_SIZE) & (Alignment <= BINNED2_MINIMUM_ALIGNMENT)) // one branch, not two
747 {
749 }
750 else
751 {
752 Alignment = FPlatformMath::Max<uint32>(Alignment, OsAllocationGranularity);
753 checkSlow(Alignment <= PageSize);
754 SizeOut = Align(Count, Alignment);
755 }
756 check(SizeOut >= Count);
757 return SizeOut;
758 }
759
760 virtual bool ValidateHeap() override;
761 virtual void Trim(bool bTrimThreadCaches) override;
762 virtual void SetupTLSCachesOnCurrentThread() override;
764 virtual const TCHAR* GetDescriptiveName() override;
765 virtual void UpdateStats() override;
766 virtual void OnMallocInitialized() override;
767 virtual void OnPreFork() override;
768 virtual void OnPostFork() override;
769 // End FMalloc interface.
770
772 void* MallocExternalSmall(SIZE_T Size, uint32 Alignment);
773 void* MallocExternalLarge(SIZE_T Size, uint32 Alignment);
774 void* ReallocExternal(void* Ptr, SIZE_T NewSize, uint32 Alignment);
775 void FreeExternal(void *Ptr);
776 bool GetAllocationSizeExternal(void* Ptr, SIZE_T& SizeOut);
777
778 void CanaryTest(const FFreeBlock* Block) const;
779 void CanaryFail(const FFreeBlock* Block) const;
780
783#endif
784 virtual void GetAllocatorStats( FGenericMemoryStats& out_Stats ) override;
785 /** Dumps current allocator stats to the log. */
786 virtual void DumpAllocatorStats(class FOutputDevice& Ar) override;
787
788 static uint16 SmallBlockSizesReversed[BINNED2_SMALL_POOL_COUNT]; // this is reversed to get the smallest elements on our main cache line
790 static uint32 Binned2TlsSlot;
791 static uint32 PageSize;
793 // Mapping of sizes to small table indices
795
797 {
798 auto Index = ((Size + BINNED2_MINIMUM_ALIGNMENT - 1) >> BINNED2_MINIMUM_ALIGNMENT_SHIFT);
799 checkSlow(Index >= 0 && Index <= (BINNED2_MAX_SMALL_POOL_SIZE >> BINNED2_MINIMUM_ALIGNMENT_SHIFT)); // and it should be in the table
800 uint32 PoolIndex = uint32(MemSizeToIndex[Index]);
801 checkSlow(PoolIndex >= 0 && PoolIndex < BINNED2_SMALL_POOL_COUNT);
802 return PoolIndex;
803 }
804 FORCEINLINE uint32 PoolIndexToBlockSize(uint32 PoolIndex)
805 {
807 }
808};
809
810#define BINNED2_INLINE (1)
811#if BINNED2_INLINE // during development, it helps with iteration time to not include these here, but rather in the .cpp
812 #if PLATFORM_USES_FIXED_GMalloc_CLASS && !FORCE_ANSI_ALLOCATOR && USE_MALLOC_BINNED2
813 #define FMEMORY_INLINE_FUNCTION_DECORATOR FORCEINLINE
814 #define FMEMORY_INLINE_GMalloc (FMallocBinned2::MallocBinned2)
815 #include "FMemory.inl" // IWYU pragma: export
816 #endif
817#endif
#define checkSlow(expr)
#define check(expr)
#define UE_BUILD_SHIPPING
Definition Build.h:4
#define UE_SERVER
Definition Build.h:45
#define WITH_EDITOR
Definition Build.h:7
#define AGGRESSIVE_MEMORY_SAVING
Definition Build.h:413
EBlockCanary
Definition Enums.h:34017
#define DEFAULT_SERVER_FAKE_FORKS
Definition Fork.h:9
#define BINNED2_MAX_GMallocBinned2MaxBundlesBeforeRecycle
#define BINNED2_BOOKKEEPING_AT_THE_END_OF_LARGEBLOCK
#define DEFAULT_GMallocBinned2BundleCount
#define BINNED2_ALLOCATOR_STATS_VALIDATION
#define BINNED2_SMALL_POOL_COUNT
#define BINNED2_MINIMUM_ALIGNMENT_SHIFT
#define GMallocBinned2PerThreadCaches
#define BINNED2_INLINE
#define DEFAULT_GMallocBinned2PerThreadCaches
#define BINNED2_LARGE_ALLOC
#define DEFAULT_GMallocBinned2MoveOSFreesOffTimeCriticalThreads
#define DEFAULT_GMallocBinned2BundleSize
#define BINNED2_ALLOW_RUNTIME_TWEAKING
#define DEFAULT_GMallocBinned2AllocExtra
#define BINNED2_MAX_CACHED_OS_FREES_BYTE_LIMIT
#define BINNED2_MAX_SMALL_POOL_SIZE
#define GMallocBinned2BundleSize
#define GMallocBinned2BundleCount
#define BINNED2_ALLOCATOR_STATS
#define BINNED2_MAX_CACHED_OS_FREES
#define BINNED2_FORK_SUPPORT
#define BINNED2_MINIMUM_ALIGNMENT
@ DEFAULT_ALIGNMENT
Definition MemoryBase.h:24
#define MAX_uint16
#define MAX_uint8
#define PLATFORM_USES_FIXED_GMalloc_CLASS
Definition Platform.h:413
#define PLATFORM_ANDROID
Definition Platform.h:29
#define FORCEINLINE
Definition Platform.h:644
#define PLATFORM_UNIX
Definition Platform.h:59
#define FORCE_ANSI_ALLOCATOR
#define UE_USE_VERYLARGEPAGEALLOCATOR
FWindowsCriticalSection FCriticalSection
#define PLATFORM_64BITS
FWindowsPlatformMath FPlatformMath
FWindowsPlatformTLS FPlatformTLS
virtual ~FMallocBinned2()
virtual FORCEINLINE void * Malloc(SIZE_T Size, uint32 Alignment) override
static uint8 MemSizeToIndex[1+(BINNED2_MAX_SMALL_POOL_SIZE > > BINNED2_MINIMUM_ALIGNMENT_SHIFT)]
void CanaryTest(const FFreeBlock *Block) const
FORCEINLINE bool IsOSAllocation(const void *Ptr)
void FreeExternal(void *Ptr)
virtual FORCEINLINE bool GetAllocationSize(void *Ptr, SIZE_T &SizeOut) override
virtual void UpdateStats() override
void * MallocExternalLarge(SIZE_T Size, uint32 Alignment)
void * MallocExternalSmall(SIZE_T Size, uint32 Alignment)
FORCEINLINE void * MallocSelect(SIZE_T Size, uint32 Alignment)
virtual void DumpAllocatorStats(class FOutputDevice &Ar) override
FORCEINLINE void * MallocInline(SIZE_T Size, uint32 Alignment)
FCriticalSection Mutex
static uint16 SmallBlockSizesReversed[BINNED2_SMALL_POOL_COUNT]
virtual void OnPreFork() override
virtual void GetAllocatorStats(FGenericMemoryStats &out_Stats) override
TCachedOSPageAllocator< BINNED2_MAX_CACHED_OS_FREES, BINNED2_MAX_CACHED_OS_FREES_BYTE_LIMIT > CachedOSPageAllocator
virtual FORCEINLINE void Free(void *Ptr) override
void CanaryFail(const FFreeBlock *Block) const
FORCEINLINE uint32 PoolIndexToBlockSize(uint32 PoolIndex)
FPtrToPoolMapping PtrToPoolMapping
virtual bool ValidateHeap() override
virtual FORCEINLINE SIZE_T QuantizeSize(SIZE_T Count, uint32 Alignment) override
virtual const TCHAR * GetDescriptiveName() override
static constexpr EBlockCanary CurrentCanary
PoolHashBucket * HashBuckets
static uint32 OsAllocationGranularity
static FORCEINLINE FFreeBlock * GetPoolHeaderFromPointer(void *Ptr)
FORCEINLINE void FreeInline(void *Ptr)
virtual bool IsInternallyThreadSafe() const override
FORCEINLINE uint32 BoundSizeToPoolIndex(SIZE_T Size)
void * ReallocExternal(void *Ptr, SIZE_T NewSize, uint32 Alignment)
void FlushCurrentThreadCache()
virtual FORCEINLINE void * Realloc(void *Ptr, SIZE_T NewSize, uint32 Alignment) override
static FMallocBinned2 * MallocBinned2
FPoolTable SmallPoolTables[BINNED2_SMALL_POOL_COUNT]
bool GetAllocationSizeExternal(void *Ptr, SIZE_T &SizeOut)
static uint32 Binned2TlsSlot
virtual void OnPostFork() override
FORCEINLINE void * ReallocInline(void *Ptr, SIZE_T NewSize, uint32 Alignment)
virtual void OnMallocInitialized() override
virtual void ClearAndDisableTLSCachesOnCurrentThread() override
virtual void Trim(bool bTrimThreadCaches) override
static FORCEINLINE bool UseSmallAlloc(SIZE_T Size, uint32 Alignment)
PoolHashBucket * HashBucketFreeList
static uint32 PageSize
virtual void SetupTLSCachesOnCurrentThread() override
FORCEINLINE FBundleNode * PopHead()
FORCEINLINE void Reset()
FORCEINLINE void PushHead(FBundleNode *Node)
FBundleNode * NextNodeInCurrentBundle
FORCEINLINE uint32 GetNumFreeRegularBlocks() const
FORCEINLINE FFreeBlock(uint32 InPageSize, uint16 InBlockSize, uint8 InPoolIndex, EBlockCanary InCanary)
FORCEINLINE void * AllocateRegularBlock()
FORCEINLINE bool PushToFront(void *InPtr, uint32 InPoolIndex, uint32 InBlockSize)
FORCEINLINE void * PopFromFront(uint32 InPoolIndex)
bool ObtainPartial(uint32 InPoolIndex)
FBundleNode * RecyleFull(uint32 InPoolIndex)
FORCEINLINE bool CanPushToFront(uint32 InPoolIndex, uint32 InBlockSize)
FBundleNode * PopBundles(uint32 InPoolIndex)
static FORCEINLINE FPerThreadFreeBlockLists * Get()
FORCEINLINE bool CanFree(uint32 InPoolIndex, uint32 InBlockSize)
bool ObtainRecycledPartial(uint32 InPoolIndex)
FORCEINLINE bool Free(void *InPtr, uint32 InPoolIndex, uint32 InBlockSize)
FFreeBlockList FreeLists[BINNED2_SMALL_POOL_COUNT]
FORCEINLINE void * Malloc(uint32 InPoolIndex)
FBundleNode * PopBundles(uint32 InPoolIndex)
FBundleNode * RecycleFullBundle(uint32 InPoolIndex)
FPoolInfo & GetFrontPool()
const FPoolInfo & GetFrontPool() const
void LinkToFront(FPoolInfo *Pool)
FPoolInfo & PushNewPoolToFront(FMallocBinned2 &Allocator, uint32 InBytes, uint32 InPoolIndex)
FORCEINLINE uint64 GetMaxHashBuckets() const
FORCEINLINE void GetHashBucketAndPoolIndices(const void *InPtr, uint32 &OutBucketIndex, UPTRINT &OutBucketCollision, uint32 &OutPoolIndex) const
FPtrToPoolMapping(uint32 InPageSize, uint64 InNumPoolsPerPage, uint64 AddressLimit)
void Init(uint32 InPageSize, uint64 InNumPoolsPerPage, uint64 AddressLimit)
static FORCEINLINE void * GetTlsValue(uint32 SlotIndex)