UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
ConcurrentLinearAllocator.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include <atomic>
6#include <type_traits>
7
8#include "HAL/Memory.h"
9#include "HAL/UnrealMemory.h"
12#include "Templates/UniquePtr.h"
15#include "Misc/MemStack.h"
16
17#if PLATFORM_HAS_ASAN_INCLUDE
18# include <sanitizer/asan_interface.h>
19# if defined(__SANITIZE_ADDRESS__)
20# define IS_ASAN_ENABLED 1
21# elif defined(__has_feature)
22# if __has_feature(address_sanitizer)
23# define IS_ASAN_ENABLED 1
24# else
25# define IS_ASAN_ENABLED 0
26# endif
27# else
28# define IS_ASAN_ENABLED 0
29# endif
30#else
31# define ASAN_POISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size))
32# define ASAN_UNPOISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size))
33# define IS_ASAN_ENABLED 0
34#endif
35
36
38{
39 static constexpr bool SupportsAlignment = true;
40 static constexpr bool UsesFMalloc = true;
42
43 inline static void* Malloc(SIZE_T Size, uint32 Alignment)
44 {
46 {
47 // There is no public function to create GMalloc and this will do it for us.
48 FMemory::Free(FMemory::Malloc(0));
50 }
51 return FMEMORY_INLINE_GMalloc->Malloc(Size, Alignment);
52 }
53
54 UE_FORCEINLINE_HINT static void Free(void* Pointer, SIZE_T Size)
55 {
56 FMEMORY_INLINE_GMalloc->Free(Pointer);
57 }
58};
59
60UE_DEPRECATED(5.6, "FOsAllocator is deprecated, use FAlignedAllocator instead")
62
65{
66 struct FTLSCleanup
67 {
68 void* Block = nullptr;
69
70 ~FTLSCleanup()
71 {
72 if(Block)
73 {
74 Allocator::Free(Block, BlockSize);
75 }
76 }
77 };
78
79 inline static void* SwapBlock(void* NewBlock)
80 {
81 static thread_local FTLSCleanup Tls;
82 void* Ret = Tls.Block;
83 Tls.Block = NewBlock;
84 return Ret;
85 }
86
87public:
88 static constexpr bool SupportsAlignment = Allocator::SupportsAlignment;
89 static constexpr bool UsesFMalloc = Allocator::UsesFMalloc;
90 static constexpr uint32 MaxAlignment = Allocator::MaxAlignment;
91
92 inline static void* Malloc(SIZE_T Size, uint32 Alignment)
93 {
94 if (Size == BlockSize)
95 {
96 void* Pointer = SwapBlock(nullptr);
97 if (Pointer != nullptr)
98 {
99 return Pointer;
100 }
101 }
102 return Allocator::Malloc(Size, Alignment);
103 }
104
105 inline static void Free(void* Pointer, SIZE_T Size)
106 {
107 if (Size == BlockSize)
108 {
109 Pointer = SwapBlock(Pointer);
110 if (Pointer == nullptr)
111 {
112 return;
113 }
114 }
115 return Allocator::Free(Pointer, Size);
116 }
117};
118
119template <uint32 BlockSize, typename Allocator = FAlignedAllocator>
121{
122public:
123 static_assert(BlockSize == FPageAllocator::PageSize, "Only 64k pages are supported with this cache.");
124 static constexpr bool SupportsAlignment = Allocator::SupportsAlignment;
125 static constexpr bool UsesFMalloc = Allocator::UsesFMalloc;
126 static constexpr uint32 MaxAlignment = Allocator::MaxAlignment;
127
128 inline static void* Malloc(SIZE_T Size, uint32 Alignment)
129 {
130 if (Size == BlockSize)
131 {
132 return FPageAllocator::Get().Alloc(Alignment);
133 }
134 else
135 {
136 return Allocator::Malloc(Size, Alignment);
137 }
138 }
139
140 inline static void Free(void* Pointer, SIZE_T Size)
141 {
142 if (Size == BlockSize)
143 {
144 FPageAllocator::Get().Free(Pointer);
145 }
146 else
147 {
148 Allocator::Free(Pointer, Size);
149 }
150 }
151};
152
154{
155 static constexpr uint32 BlockSize = 64 * 1024; // Blocksize used to allocate from
156 static constexpr bool AllowOversizedBlocks = true; // The allocator supports oversized Blocks and will store them in a seperate Block with counter 1
157 static constexpr bool RequiresAccurateSize = true; // GetAllocationSize returning the accurate size of the allocation otherwise it could be relaxed to return the size to the end of the Block
158 static constexpr bool InlineBlockAllocation = false; // Inline or Noinline the BlockAllocation which can have an impact on Performance
159 static constexpr const char* TagName = "DefaultLinear";
160
162};
163
164/*************************************************************************************************************************
165 * This fast linear allocator can be used for temporary allocations, and is best suited for allocations that are
166 * produced and consumed on different threads and within the lifetime of a frame. Although the lifetime of any
167 * individual allocation is not hard-tied to a frame (tracking is done using the FBlockHeader::NumAllocations atomic
168 * variable), the application will eventually run OOM if allocations are not cleaned up in a timely manner.
169 *
170 * There is a fast-path version of the allocator that skips AllocationHeaders by aligning the BlockHeader with the
171 * BlockSize, so that headers can easily be found by AligningDown the address of the Allocation itself.
172 * The allocator works by allocating a larger block in TLS which has a Header at the front which contains the atomic,
173 * and all allocations are than allocated from this block:
174 *
175 * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
176 * | FBlockHeader(atomic counter etc.) | Alignment Waste | FAllocationHeader(size, optional) | Memory used for Allocation | Alignment Waste | FAllocationHeader(size, optional) | Memory used for Allocation | FreeSpace ...
177 * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
178 *
179 * The allocator is most often used concurrently, but also supports single-threaded use cases, so it can be used for an
180 * array scratchpad.
181 */
187
188template <typename BlockAllocationTag, ELinearAllocatorThreadPolicy ThreadPolicy>
190{
191 static constexpr bool SupportsFastPath = ((BlockAllocationTag::BlockSize <= (64 * 1024)) && (FPlatformProperties::GetMaxSupportedVirtualMemoryAlignment() >= 64 * 1024))
192 && FMath::IsPowerOfTwo(BlockAllocationTag::BlockSize) // AlignDown only works with Pow2
193 && !IS_ASAN_ENABLED && !BlockAllocationTag::RequiresAccurateSize // Only enabled when not using ASAN or when the Size of an allocation is not required
194 && BlockAllocationTag::Allocator::SupportsAlignment; // The allocator needs to align the BlockHeader by BlockSize to find the BlockHeader
195
196 LLM(static constexpr ELLMAllocType LLMAllocationType = BlockAllocationTag::Allocator::UsesFMalloc ? ELLMAllocType::FMalloc : ELLMAllocType::None;)
197
198 struct FBlockHeader;
199
200 class FAllocationHeader
201 {
202 public:
203 inline FAllocationHeader(FBlockHeader* InBlockHeader, SIZE_T InAllocationSize)
204 {
207 BlockHeaderOffset = uint32(Offset);
208
210 AllocationSize = uint32(InAllocationSize);
211 }
212
213 UE_FORCEINLINE_HINT FBlockHeader* GetBlockHeader() const
214 {
215 return reinterpret_cast<FBlockHeader*>(uintptr_t(this) - BlockHeaderOffset);
216 }
217
218 UE_FORCEINLINE_HINT SIZE_T GetAllocationSize() const
219 {
220 return SIZE_T(AllocationSize);
221 }
222
223 private:
224 uint32 BlockHeaderOffset; //this is the negative offset from the allocation to the BlockHeader.
225 uint32 AllocationSize; //this is the size of the allocation following the AllocationHeader.
226 };
227
228 static inline FAllocationHeader* GetAllocationHeader(void* Pointer)
229 {
230 if (SupportsFastPath)
231 {
232 return nullptr;
233 }
234 else
235 {
236 return reinterpret_cast<FAllocationHeader*>(Pointer) - 1;
237 }
238 }
239
240 struct FBlockHeader
241 {
242 UE_FORCEINLINE_HINT FBlockHeader()
243 //We need to reserve space for at least one BlockHeader as well as one AllocationHeader
244 : NextAllocationPtr(uintptr_t(this) + sizeof(FBlockHeader) + sizeof(FAllocationHeader))
245 {
246 }
247
249 {
251 unsigned int FetchSub(unsigned int N, std::memory_order)
252 {
253 unsigned int OriginalValue = Value;
254 Value -= N;
255 return OriginalValue;
256 }
257
258 void Store(unsigned int N, std::memory_order)
259 {
260 Value = N;
261 }
262
263 unsigned int Value;
264 };
265
267 {
269 unsigned int FetchSub(unsigned int N, std::memory_order Order)
270 {
271 return Value.fetch_sub(N, Order);
272 }
273
274 void Store(unsigned int N, std::memory_order Order)
275 {
276 return Value.store(N, Order);
277 }
278
279 std::atomic_uint Value;
280 };
281
282 using NumAllocationsType = std::conditional_t<ThreadPolicy == ELinearAllocatorThreadPolicy::ThreadSafe, FAtomicUInt, FPlainUInt>;
283
284 NumAllocationsType NumAllocations { .Value = UINT_MAX }; //this is shared between threads and tracks the number of live allocations (plus UINT_MAX) and we will fix this up when we close a block.
285 uint8 Padding[PLATFORM_CACHE_LINE_SIZE - sizeof(std::atomic_uint)]; //avoid false sharing
286 uintptr_t NextAllocationPtr; //this is the next address we are trying to allocate from.
287 unsigned int Num = 0; //this tracks the TLS local Number of allocation from a given Block
288 };
289
290 struct FTLSCleanup
291 {
292 FBlockHeader* Header = nullptr;
293
294 ~FTLSCleanup()
295 {
296 if(Header)
297 {
298 Header->NextAllocationPtr = uintptr_t(Header) + BlockAllocationTag::BlockSize;
299 const uint32 DeltaCount = UINT_MAX - Header->Num;
300
301 // on the allocating side we only need to do a single atomic to reduce contention with the deletions
302 // this will leave the atomic in a state where it only counts the number of live allocations (before it was based of UINT_MAX)
303 if (Header->NumAllocations.FetchSub(DeltaCount, std::memory_order_acq_rel) == DeltaCount)
304 {
305 //if all allocations are already freed we can reuse the Block again
306 Header->~FBlockHeader();
307 ASAN_UNPOISON_MEMORY_REGION( Header, BlockAllocationTag::BlockSize );
309 BlockAllocationTag::Allocator::Free(Header, BlockAllocationTag::BlockSize);
310 }
311 }
312 }
313 };
314
315 FORCENOINLINE static void AllocateBlock(FBlockHeader*& Header)
316 {
317 if constexpr (!BlockAllocationTag::InlineBlockAllocation)
318 {
319 static_assert(BlockAllocationTag::BlockSize >= sizeof(FBlockHeader) + sizeof(FAllocationHeader));
320 uint32 BlockAlignment = SupportsFastPath ? BlockAllocationTag::BlockSize : alignof(FBlockHeader);
321 Header = new (BlockAllocationTag::Allocator::Malloc(BlockAllocationTag::BlockSize, BlockAlignment)) FBlockHeader;
323 checkSlow(IsAligned(Header, BlockAlignment));
324 if constexpr (!SupportsFastPath)
325 {
326 ASAN_POISON_MEMORY_REGION( Header + 1, BlockAllocationTag::BlockSize - sizeof(FBlockHeader) );
327 }
328
329 static thread_local FTLSCleanup Cleanup;
330 new (&Cleanup) FTLSCleanup{ Header };
331 }
332 }
333
334public:
335 template<uint32 Alignment> //this can help the compiler folding away the alignment when it is statically known
337 {
338 return Malloc(Size, Alignment);
339 }
340
341 template<typename T>
343 {
344 return Malloc(sizeof(T), alignof(T));
345 }
346
347 static inline void* Malloc(SIZE_T Size, uint32 Alignment)
348 {
349 checkSlow(Alignment >= 1 && FMath::IsPowerOfTwo(Alignment));
350 if constexpr (!SupportsFastPath)
351 {
352 Alignment = (Alignment < alignof(FAllocationHeader)) ? alignof(FAllocationHeader) : Alignment;
353#if IS_ASAN_ENABLED
354 // Make sure FAllocationHeader is 8 bytes aligned so poison / unpoison doesnt end up as false positive
355 Alignment = Align(Alignment, 8);
356#endif
357 }
358
359 static thread_local FBlockHeader* Header;
360 if (Header == nullptr)
361 {
362 AllocateNewBlock:
363 if constexpr (BlockAllocationTag::InlineBlockAllocation)
364 {
365 static_assert(BlockAllocationTag::BlockSize >= sizeof(FBlockHeader) + sizeof(FAllocationHeader));
366 uint32 BlockAlignment = SupportsFastPath ? BlockAllocationTag::BlockSize : alignof(FBlockHeader);
367 Header = new (BlockAllocationTag::Allocator::Malloc(BlockAllocationTag::BlockSize, BlockAlignment)) FBlockHeader;
369 checkSlow(IsAligned(Header, BlockAlignment));
370 if constexpr (!SupportsFastPath)
371 {
372 ASAN_POISON_MEMORY_REGION( Header + 1, BlockAllocationTag::BlockSize - sizeof(FBlockHeader) );
373 }
374
375 static thread_local FTLSCleanup Cleanup;
376 new (&Cleanup) FTLSCleanup{ Header };
377 }
378 else
379 {
380 AllocateBlock(Header);
381 }
382 }
383
385 if constexpr (SupportsFastPath)
386 {
387 uintptr_t AlignedOffset = Align(Header->NextAllocationPtr, Alignment);
388 if (AlignedOffset + Size <= uintptr_t(Header) + BlockAllocationTag::BlockSize)
389 {
390 Header->NextAllocationPtr = AlignedOffset + Size;
391 Header->Num++;
392
393 MemoryTrace_Alloc(uint64(AlignedOffset), Size, Alignment);
394 LLM_IF_ENABLED( FLowLevelMemTracker::Get().OnLowLevelAlloc(ELLMTracker::Default, reinterpret_cast<void*>(AlignedOffset), Size, ELLMTag::LinearAllocator, LLMAllocationType) );
395 return reinterpret_cast<void*>(AlignedOffset);
396 }
397
398 // The cold path of the code starts here
399 constexpr SIZE_T HeaderSize = sizeof(FBlockHeader);
400 if constexpr (BlockAllocationTag::AllowOversizedBlocks)
401 {
402 //support for oversized Blocks
403 if (HeaderSize + Size + Alignment > BlockAllocationTag::BlockSize)
404 {
405 FBlockHeader* LargeHeader = new (BlockAllocationTag::Allocator::Malloc(HeaderSize + Size + Alignment, BlockAllocationTag::BlockSize)) FBlockHeader;
407 checkSlow(IsAligned(LargeHeader, alignof(FBlockHeader)));
408
409 uintptr_t LargeAlignedOffset = Align(LargeHeader->NextAllocationPtr, Alignment);
410 LargeHeader->NextAllocationPtr = uintptr_t(LargeHeader) + HeaderSize + Size + Alignment;
411 LargeHeader->NumAllocations.Store(1, std::memory_order_release);
412
414
416 LLM_IF_ENABLED( FLowLevelMemTracker::Get().OnLowLevelAlloc(ELLMTracker::Default, reinterpret_cast<void*>(LargeAlignedOffset), Size, ELLMTag::LinearAllocator, LLMAllocationType) );
417 return reinterpret_cast<void*>(LargeAlignedOffset);
418 }
419 }
420 check(HeaderSize + Size + Alignment <= BlockAllocationTag::BlockSize);
421 }
422 else
423 {
424 uintptr_t AlignedOffset = Align(Header->NextAllocationPtr, Alignment);
425 if (AlignedOffset + Size <= uintptr_t(Header) + BlockAllocationTag::BlockSize)
426 {
427 Header->NextAllocationPtr = AlignedOffset + Size + sizeof(FAllocationHeader);
428 Header->Num++;
429
430 FAllocationHeader* AllocationHeader = reinterpret_cast<FAllocationHeader*>(AlignedOffset) - 1;
431 ASAN_UNPOISON_MEMORY_REGION( AllocationHeader, sizeof(FAllocationHeader) + Size );
432 new (AllocationHeader) FAllocationHeader(Header, Size);
433 ASAN_POISON_MEMORY_REGION( AllocationHeader, sizeof(FAllocationHeader) );
434
435 MemoryTrace_Alloc(uint64(AllocationHeader), Size + sizeof(FAllocationHeader), Alignment);
436 LLM_IF_ENABLED( FLowLevelMemTracker::Get().OnLowLevelAlloc(ELLMTracker::Default, AllocationHeader, Size + sizeof(FAllocationHeader), ELLMTag::LinearAllocator, LLMAllocationType) );
437 return reinterpret_cast<void*>(AlignedOffset);
438 }
439
440 // The cold path of the code starts here
441 constexpr SIZE_T HeaderSize = sizeof(FBlockHeader) + sizeof(FAllocationHeader);
442 if constexpr (BlockAllocationTag::AllowOversizedBlocks)
443 {
444 //support for oversized Blocks
445 if (HeaderSize + Size + Alignment > BlockAllocationTag::BlockSize)
446 {
447 FBlockHeader* LargeHeader = new (BlockAllocationTag::Allocator::Malloc(HeaderSize + Size + Alignment, alignof(FBlockHeader))) FBlockHeader;
449 checkSlow(IsAligned(LargeHeader, alignof(FBlockHeader)));
450
451 uintptr_t LargeAlignedOffset = Align(LargeHeader->NextAllocationPtr, Alignment);
452 LargeHeader->NextAllocationPtr = uintptr_t(LargeHeader) + HeaderSize + Size + Alignment;
453 LargeHeader->NumAllocations.Store(1, std::memory_order_release);
454
456 FAllocationHeader* AllocationHeader = new (reinterpret_cast<FAllocationHeader*>(LargeAlignedOffset) - 1) FAllocationHeader(LargeHeader, Size);
457 ASAN_POISON_MEMORY_REGION( AllocationHeader, sizeof(FAllocationHeader) );
458
459 MemoryTrace_Alloc(uint64(AllocationHeader), Size + sizeof(FAllocationHeader), Alignment);
460 LLM_IF_ENABLED( FLowLevelMemTracker::Get().OnLowLevelAlloc(ELLMTracker::Default, AllocationHeader, Size + sizeof(FAllocationHeader), ELLMTag::LinearAllocator, LLMAllocationType) );
461 return reinterpret_cast<void*>(LargeAlignedOffset);
462 }
463 }
464 check(HeaderSize + Size + Alignment <= BlockAllocationTag::BlockSize);
465 }
466
467 //allocation of a new Block
468 Header->NextAllocationPtr = uintptr_t(Header) + BlockAllocationTag::BlockSize;
469 const uint32 DeltaCount = UINT_MAX - Header->Num;
470
471 // on the allocating side we only need to do a single atomic to reduce contention with the deletions
472 // this will leave the atomic in a state where it only counts the number of live allocations (before it was based of UINT_MAX)
473 if (Header->NumAllocations.FetchSub(DeltaCount, std::memory_order_acq_rel) == DeltaCount)
474 {
475 //if all allocations are already freed we can reuse the Block again
476 Header->~FBlockHeader();
477 Header = new (Header) FBlockHeader;
478 ASAN_POISON_MEMORY_REGION( Header + 1, BlockAllocationTag::BlockSize - sizeof(FBlockHeader) );
479 goto AllocateNewItem;
480 }
481
482 goto AllocateNewBlock;
483 }
484
485 static inline void Free(void* Pointer)
486 {
487 if(Pointer != nullptr)
488 {
489 if constexpr (SupportsFastPath)
490 {
491 MemoryTrace_Free(uint64(Pointer));
492 LLM_IF_ENABLED(FLowLevelMemTracker::Get().OnLowLevelFree(ELLMTracker::Default, Pointer, LLMAllocationType));
493 FBlockHeader* Header = reinterpret_cast<FBlockHeader*>(AlignDown(Pointer, BlockAllocationTag::BlockSize));
494
495 // this deletes complete blocks when the last allocation is freed
496 if (Header->NumAllocations.FetchSub(1, std::memory_order_acq_rel) == 1)
497 {
498 const uintptr_t NextAllocationPtr = Header->NextAllocationPtr;
499 Header->~FBlockHeader();
501 BlockAllocationTag::Allocator::Free(Header, NextAllocationPtr - uintptr_t(Header));
502 }
503 }
504 else
505 {
506 FAllocationHeader* AllocationHeader = GetAllocationHeader(Pointer);
507 ASAN_UNPOISON_MEMORY_REGION( AllocationHeader, sizeof(FAllocationHeader) );
509 LLM_IF_ENABLED(FLowLevelMemTracker::Get().OnLowLevelFree(ELLMTracker::Default, AllocationHeader, LLMAllocationType));
510 FBlockHeader* Header = AllocationHeader->GetBlockHeader();
511 ASAN_POISON_MEMORY_REGION( AllocationHeader, sizeof(FAllocationHeader) + AllocationHeader->GetAllocationSize() );
512
513 // this deletes complete blocks when the last allocation is freed
514 if (Header->NumAllocations.FetchSub(1, std::memory_order_acq_rel) == 1)
515 {
516 const uintptr_t NextAllocationPtr = Header->NextAllocationPtr;
517 Header->~FBlockHeader();
518 ASAN_UNPOISON_MEMORY_REGION( Header, NextAllocationPtr - uintptr_t(Header) );
520 BlockAllocationTag::Allocator::Free(Header, NextAllocationPtr - uintptr_t(Header));
521 }
522 }
523 }
524 }
525
526 static inline SIZE_T GetAllocationSize(void* Pointer)
527 {
528 if (Pointer)
529 {
530 if constexpr (SupportsFastPath)
531 {
532 return Align(uintptr_t(Pointer), BlockAllocationTag::BlockSize) - uintptr_t(Pointer);
533 }
534 else
535 {
536 FAllocationHeader* AllocationHeader = GetAllocationHeader(Pointer);
537 ASAN_UNPOISON_MEMORY_REGION( AllocationHeader, sizeof(FAllocationHeader) );
538 SIZE_T Size = AllocationHeader->GetAllocationSize();
539 ASAN_POISON_MEMORY_REGION( AllocationHeader, sizeof(FAllocationHeader) );
540 return Size;
541 }
542 }
543 return 0;
544 }
545
546 static inline void* Realloc(void* Old, SIZE_T Size, uint32 Alignment)
547 {
548 void* New = nullptr;
549 if(Size != 0)
550 {
551 New = Malloc(Size, Alignment);
552 SIZE_T OldSize = GetAllocationSize(Old);
553 if(OldSize != 0)
554 {
555 memcpy(New, Old, Size < OldSize ? Size : OldSize);
556 }
557 }
558 Free(Old);
559 return New;
560 }
561};
562
563template <typename T>
565
568
569template<typename ObjectType, typename BlockAllocationTag = FDefaultBlockAllocationTag>
571{
572public:
573 inline static void* operator new(size_t Size)
574 {
575 static_assert(TIsDerivedFrom<ObjectType, TConcurrentLinearObject<ObjectType, BlockAllocationTag>>::Value, "TConcurrentLinearObject must be base of it's ObjectType (see CRTP)");
576 static_assert(alignof(ObjectType) <= BlockAllocationTag::Allocator::MaxAlignment);
578 }
579
580 inline static void* operator new(size_t Size, void* Object)
581 {
582 static_assert(TIsDerivedFrom<ObjectType, TConcurrentLinearObject<ObjectType, BlockAllocationTag>>::Value, "TConcurrentLinearObject must be base of it's ObjectType (see CRTP)");
583 return Object;
584 }
585
586 inline static void* operator new[](size_t Size)
587 {
588 static_assert(alignof(ObjectType) <= BlockAllocationTag::Allocator::MaxAlignment);
590 }
591
592 inline static void* operator new(size_t Size, std::align_val_t Align)
593 {
594 static_assert(alignof(ObjectType) <= BlockAllocationTag::Allocator::MaxAlignment);
595 checkSlow(size_t(Align) == alignof(ObjectType));
597 }
598
599 inline static void* operator new[](size_t Size, std::align_val_t Align)
600 {
601 static_assert(alignof(ObjectType) <= BlockAllocationTag::Allocator::MaxAlignment);
602 checkSlow(size_t(Align) == alignof(ObjectType));
604 }
605
606 UE_FORCEINLINE_HINT static void operator delete(void* Ptr)
607 {
609 }
610
611 UE_FORCEINLINE_HINT static void operator delete[](void* Ptr)
612 {
614 }
615};
616
617namespace UE::Core::Private
618{
620}
621
622template <typename BlockAllocationTag, ELinearAllocatorThreadPolicy ThreadPolicy>
624{
625public:
627
628 enum { NeedsElementType = true };
629 enum { RequireRangeCheck = true };
630
631 template<typename ElementType>
633 {
634 public:
635 ForElementType() = default;
637 {
638 if (Data)
639 {
641 }
642 }
649 {
650 checkSlow(this != &Other);
651
652 if (Data)
653 {
655 }
656
657 Data = Other.Data;
658 Other.Data = nullptr;
659 }
660 inline ElementType* GetAllocation() const
661 {
662 return Data;
663 }
664 void ResizeAllocation(SizeType CurrentNum, SizeType NewMax, SIZE_T NumBytesPerElement)
665 {
666 static_assert(sizeof(int32) <= sizeof(SIZE_T), "SIZE_T is expected to be larger than int32");
667
668 // Check for under/overflow
670 {
672 }
673
674 static_assert(alignof(ElementType) <= BlockAllocationTag::Allocator::MaxAlignment);
675 Data = (ElementType*)TLinearAllocatorBase<BlockAllocationTag, ThreadPolicy>::Realloc(Data, NewMax * NumBytesPerElement, alignof(ElementType));
676 }
678 {
679 return DefaultCalculateSlackReserve(NewMax, NumBytesPerElement, false);
680 }
681 SizeType CalculateSlackShrink(SizeType NewMax, SizeType CurrentMax, SIZE_T NumBytesPerElement) const
682 {
683 return DefaultCalculateSlackShrink(NewMax, CurrentMax, NumBytesPerElement, false);
684 }
685 SizeType CalculateSlackGrow(SizeType NewMax, SizeType CurrentMax, SIZE_T NumBytesPerElement) const
686 {
687 return DefaultCalculateSlackGrow(NewMax, CurrentMax, NumBytesPerElement, false);
688 }
689
690 SIZE_T GetAllocatedSize(SizeType CurrentMax, SIZE_T NumBytesPerElement) const
691 {
692 return CurrentMax * NumBytesPerElement;
693 }
694
695 bool HasAllocation() const
696 {
697 return !!Data;
698 }
699
701 {
702 return 0;
703 }
704
705
706 private:
707 ForElementType(const ForElementType&) = delete;
708 ForElementType& operator=(const ForElementType&) = delete;
709 ElementType* Data = nullptr;
710 };
711
713};
714
715template <typename BlockAllocationTag>
717
718template <typename BlockAllocationTag>
720
721template <typename BlockAllocationTag>
722struct TAllocatorTraits<TConcurrentLinearArrayAllocator<BlockAllocationTag>> : TAllocatorTraitsBase<TConcurrentLinearArrayAllocator<BlockAllocationTag>>
723{
724 enum { IsZeroConstruct = true };
725};
726
727template <typename BlockAllocationTag>
729
730template <typename BlockAllocationTag>
732
733template <typename BlockAllocationTag>
735
740
742
743//The BulkObjectAllocator can be used to atomically destroy all allocated Objects, it will properly call every destructor before deleting the memory as well
744template<typename BlockAllocationTag>
746{
748 struct FAllocation
749 {
750 virtual ~FAllocation() = default;
751 FAllocation* Next = nullptr;
752 };
753
754 template <typename T>
755 struct TObject final : FAllocation
756 {
757 TObject() = default;
758 virtual ~TObject() override
759 {
760 T* Alloc = reinterpret_cast<T*>(uintptr_t(this) + Align(sizeof(TObject<T>), alignof(T)));
761 checkSlow(IsAligned(Alloc, alignof(T)));
762
763 // We need a typedef here because VC won't compile the destructor call below if ElementType itself has a member called ElementType
764 using DestructorType = T;
765 Alloc->DestructorType::~T();
766 }
767 };
768
769 template <typename T>
770 struct TObjectArray final : FAllocation
771 {
772 inline TObjectArray(SIZE_T InNum)
773 : Num(InNum)
774 {
775 }
776
777 virtual ~TObjectArray() override
778 {
779 T* Alloc = reinterpret_cast<T*>(uintptr_t(this) + Align(sizeof(TObjectArray<T>), alignof(T)));
780 checkSlow(IsAligned(Alloc, alignof(T)));
781
782 for (SIZE_T i = 0; i < Num; i++)
783 {
784 // We need a typedef here because VC won't compile the destructor call below if ElementType itself has a member called ElementType
785 using DestructorType = T;
786 Alloc[i].DestructorType::~T();
787 }
788 }
789 SIZE_T Num;
790 };
791
792 std::atomic<FAllocation*> Next { nullptr };
793
794public:
800
801 inline void BulkDelete()
802 {
803 FAllocation* Allocation = Next.exchange(nullptr, std::memory_order_acquire);
804 while (Allocation != nullptr)
805 {
806 FAllocation* NextAllocation = Allocation->Next;
807 Allocation->~FAllocation();
809 Allocation = NextAllocation;
810 }
811 }
812
813 inline void* Malloc(SIZE_T Size, uint32 Alignment)
814 {
815 SIZE_T TotalSize = Align(sizeof(FAllocation), Alignment) + Size;
816 FAllocation* Allocation = new (TConcurrentLinearAllocator<BlockAllocationTag>::Malloc(TotalSize, FMath::Max(static_cast<uint32>(alignof(FAllocation)), Alignment))) FAllocation();
817
818 void* Alloc = Align(Allocation + 1, Alignment);
819 checkSlow(uintptr_t(Alloc) + Size - uintptr_t(Allocation) <= TotalSize);
820
821 Allocation->Next = Next.load(std::memory_order_relaxed);
822 while (!Next.compare_exchange_strong(Allocation->Next, Allocation, std::memory_order_release, std::memory_order_relaxed))
823 {
824 }
825 return Alloc;
826 }
827
829 {
830 void* Ptr = Malloc(Size, Alignment);
832 return Ptr;
833 }
834
835 template <typename T>
836 inline T* Malloc()
837 {
838 return reinterpret_cast<T*>(Malloc(sizeof(T), alignof(T)));
839 }
840
841 template <typename T>
843 {
844 void* Ptr = Malloc(sizeof(T), alignof(T));
845 FMemory::Memset(Ptr, MemsetChar, sizeof(T));
846 return reinterpret_cast<T*>(Ptr);
847 }
848
849 template <typename T>
851 {
852 return reinterpret_cast<T*>(Malloc(sizeof(T) * Num, alignof(T)));
853 }
854
855 template <typename T>
857 {
858 void* Ptr = Malloc(sizeof(T) * Num, alignof(T));
859 FMemory::Memset(Ptr, MemsetChar, sizeof(T) * Num);
860 return reinterpret_cast<T*>(Ptr);
861 }
862
863 template<typename T, typename... TArgs>
864 inline T* Create(TArgs&&... Args)
865 {
866 T* Alloc = CreateNoInit<T>();
867 new ((void*)Alloc) T(Forward<TArgs>(Args)...);
868 return Alloc;
869 }
870
871 template<typename T, typename... TArgs>
872 inline T* CreateArray(SIZE_T Num, const TArgs&... Args)
873 {
874 T* Alloc = CreateArrayNoInit<T>(Num);
875 for (SIZE_T i = 0; i < Num; i++)
876 {
877 new ((void*)(&Alloc[i])) T(Args...);
878 }
879 return Alloc;
880 }
881
882private: //Kept private, there is also deliberately no new operator overloads because those will not be able to send the ObjectType into the allocator defeating its purpose
883 template<typename T>
884 inline T* CreateNoInit()
885 {
886 SIZE_T TotalSize = Align(sizeof(TObject<T>), alignof(T)) + sizeof(T);
887 TObject<T>* Object = new (TConcurrentLinearAllocator<BlockAllocationTag>::template Malloc<FMath::Max(alignof(TObject<T>), alignof(T))>(TotalSize)) TObject<T>();
888
889 T* Alloc = reinterpret_cast<T*>(uintptr_t(Object) + Align(sizeof(TObject<T>), alignof(T)));
890 checkSlow(IsAligned(Alloc, alignof(T)));
891 checkSlow(uintptr_t(Alloc + 1) - uintptr_t(Object) <= TotalSize);
892
893 Object->Next = Next.load(std::memory_order_relaxed);
894 while (!Next.compare_exchange_weak(Object->Next, Object, std::memory_order_release, std::memory_order_relaxed))
895 {
896 }
897 return Alloc;
898 }
899
900 template<typename T>
901 inline T* CreateArrayNoInit(SIZE_T Num)
902 {
903 SIZE_T TotalSize = Align(sizeof(TObjectArray<T>), alignof(T)) + (sizeof(T) * Num);
905
906 T* Alloc = reinterpret_cast<T*>(uintptr_t(Array) + Align(sizeof(TObjectArray<T>), alignof(T)));
907 checkSlow(IsAligned(Alloc, alignof(T)));
908 checkSlow(uintptr_t(Alloc + Num) - uintptr_t(Array) <= TotalSize);
909
910 Array->Next = Next.load(std::memory_order_relaxed);
911 while (!Next.compare_exchange_weak(Array->Next, Array, std::memory_order_release, std::memory_order_relaxed))
912 {
913 }
914 return Alloc;
915 }
916};
917
constexpr T Align(T Val, uint64 Alignment)
Definition AlignmentTemplates.h:18
constexpr T AlignDown(T Val, uint64 Alignment)
Definition AlignmentTemplates.h:34
constexpr bool IsAligned(T Val, uint64 Alignment)
Definition AlignmentTemplates.h:50
#define FORCENOINLINE
Definition AndroidPlatform.h:142
#define checkSlow(expr)
Definition AssertionMacros.h:332
#define check(expr)
Definition AssertionMacros.h:314
ELinearAllocatorThreadPolicy
Definition ConcurrentLinearAllocator.h:183
#define ASAN_POISON_MEMORY_REGION(addr, size)
Definition ConcurrentLinearAllocator.h:31
#define IS_ASAN_ENABLED
Definition ConcurrentLinearAllocator.h:33
#define ASAN_UNPOISON_MEMORY_REGION(addr, size)
Definition ConcurrentLinearAllocator.h:32
UE_FORCEINLINE_HINT SizeType DefaultCalculateSlackReserve(SizeType NewMax, SIZE_T BytesPerElement, bool bAllowQuantize, uint32 Alignment=DEFAULT_ALIGNMENT)
Definition ContainerAllocationPolicies.h:223
UE_FORCEINLINE_HINT SizeType DefaultCalculateSlackShrink(SizeType NewMax, SizeType CurrentMax, SIZE_T BytesPerElement, bool bAllowQuantize, uint32 Alignment=DEFAULT_ALIGNMENT)
Definition ContainerAllocationPolicies.h:139
UE_FORCEINLINE_HINT SizeType DefaultCalculateSlackGrow(SizeType NewMax, SizeType CurrentMax, SIZE_T BytesPerElement, bool bAllowQuantize, uint32 Alignment=DEFAULT_ALIGNMENT)
Definition ContainerAllocationPolicies.h:169
#define UE_DEPRECATED(Version, Message)
Definition CoreMiscDefines.h:302
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 UE_FORCEINLINE_HINT
Definition Platform.h:723
#define UNLIKELY(x)
Definition Platform.h:857
#define PLATFORM_CACHE_LINE_SIZE
Definition Platform.h:938
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 LLM_IF_ENABLED(...)
Definition LowLevelMemTracker.h:1093
#define LLM(...)
Definition LowLevelMemTracker.h:1092
#define UE_MBC_MAX_SMALL_POOL_ALIGNMENT
Definition MallocBinnedCommon.h:46
T * New(FMemStackBase &Mem, int32 Count=1, int32 Align=DEFAULT_ALIGNMENT)
Definition MemStack.h:259
void MemoryTrace_MarkAllocAsHeap(uint64 Address, HeapId Heap)
Definition MemoryTrace.h:197
void MemoryTrace_Free(uint64 Address, HeapId RootHeap=EMemoryTraceRootHeap::SystemMemory, uint32 ExternalCallstackId=0)
Definition MemoryTrace.h:200
@ SystemMemory
Definition MemoryTrace.h:35
void MemoryTrace_UnmarkAllocAsHeap(uint64 Address, HeapId Heap)
Definition MemoryTrace.h:198
void MemoryTrace_Alloc(uint64 Address, uint64 Size, uint32 Alignment, HeapId RootHeap=EMemoryTraceRootHeap::SystemMemory, uint32 ExternalCallstackId=0)
Definition MemoryTrace.h:199
#define FMEMORY_INLINE_GMalloc
Definition Memory.h:17
@ Num
Definition MetalRHIPrivate.h:234
#define MAX_int32
Definition NumericLimits.h:25
uint32 Offset
Definition VulkanMemory.cpp:4033
uint32 Size
Definition VulkanMemory.cpp:4034
memcpy(InputBufferBase, BinkBlocksData, BinkBlocksSize)
uint8_t uint8
Definition binka_ue_file_header.h:8
uint32_t uint32
Definition binka_ue_file_header.h:6
@ PageSize
Definition MemStack.h:40
CORE_API void Free(void *Mem)
Definition MemStack.cpp:248
static CORE_API FPageAllocator & Get()
Definition MemStack.cpp:30
CORE_API void * Alloc(int32 Alignment=MIN_ALIGNMENT)
Definition MemStack.cpp:236
Definition ConcurrentLinearAllocator.h:65
static void Free(void *Pointer, SIZE_T Size)
Definition ConcurrentLinearAllocator.h:105
static void * Malloc(SIZE_T Size, uint32 Alignment)
Definition ConcurrentLinearAllocator.h:92
Definition ConcurrentLinearAllocator.h:121
static constexpr bool SupportsAlignment
Definition ConcurrentLinearAllocator.h:124
static constexpr uint32 MaxAlignment
Definition ConcurrentLinearAllocator.h:126
static void * Malloc(SIZE_T Size, uint32 Alignment)
Definition ConcurrentLinearAllocator.h:128
static constexpr bool UsesFMalloc
Definition ConcurrentLinearAllocator.h:125
static void Free(void *Pointer, SIZE_T Size)
Definition ConcurrentLinearAllocator.h:140
Definition ConcurrentLinearAllocator.h:746
void * MallocAndMemset(SIZE_T Size, uint32 Alignment, uint8 MemsetChar)
Definition ConcurrentLinearAllocator.h:828
T * MallocAndMemset(uint8 MemsetChar)
Definition ConcurrentLinearAllocator.h:842
T * Malloc()
Definition ConcurrentLinearAllocator.h:836
T * MallocArray(SIZE_T Num)
Definition ConcurrentLinearAllocator.h:850
void BulkDelete()
Definition ConcurrentLinearAllocator.h:801
T * CreateArray(SIZE_T Num, const TArgs &... Args)
Definition ConcurrentLinearAllocator.h:872
T * Create(TArgs &&... Args)
Definition ConcurrentLinearAllocator.h:864
T * MallocAndMemsetArray(SIZE_T Num, uint8 MemsetChar)
Definition ConcurrentLinearAllocator.h:856
void * Malloc(SIZE_T Size, uint32 Alignment)
Definition ConcurrentLinearAllocator.h:813
~TConcurrentLinearBulkObjectAllocator()
Definition ConcurrentLinearAllocator.h:796
Definition ConcurrentLinearAllocator.h:571
Definition ConcurrentLinearAllocator.h:190
static SIZE_T GetAllocationSize(void *Pointer)
Definition ConcurrentLinearAllocator.h:526
static void * Realloc(void *Old, SIZE_T Size, uint32 Alignment)
Definition ConcurrentLinearAllocator.h:546
static UE_FORCEINLINE_HINT void * Malloc()
Definition ConcurrentLinearAllocator.h:342
static void * Malloc(SIZE_T Size, uint32 Alignment)
Definition ConcurrentLinearAllocator.h:347
static UE_FORCEINLINE_HINT void * Malloc(SIZE_T Size)
Definition ConcurrentLinearAllocator.h:336
static void Free(void *Pointer)
Definition ConcurrentLinearAllocator.h:485
Definition ConcurrentLinearAllocator.h:633
SizeType GetInitialCapacity() const
Definition ConcurrentLinearAllocator.h:700
bool HasAllocation() const
Definition ConcurrentLinearAllocator.h:695
SizeType CalculateSlackGrow(SizeType NewMax, SizeType CurrentMax, SIZE_T NumBytesPerElement) const
Definition ConcurrentLinearAllocator.h:685
void ResizeAllocation(SizeType CurrentNum, SizeType NewMax, SIZE_T NumBytesPerElement)
Definition ConcurrentLinearAllocator.h:664
SIZE_T GetAllocatedSize(SizeType CurrentMax, SIZE_T NumBytesPerElement) const
Definition ConcurrentLinearAllocator.h:690
~ForElementType()
Definition ConcurrentLinearAllocator.h:636
void MoveToEmpty(ForElementType &Other)
Definition ConcurrentLinearAllocator.h:648
ElementType * GetAllocation() const
Definition ConcurrentLinearAllocator.h:660
SizeType CalculateSlackReserve(SizeType NewMax, SIZE_T NumBytesPerElement) const
Definition ConcurrentLinearAllocator.h:677
SizeType CalculateSlackShrink(SizeType NewMax, SizeType CurrentMax, SIZE_T NumBytesPerElement) const
Definition ConcurrentLinearAllocator.h:681
Definition ConcurrentLinearAllocator.h:624
@ NeedsElementType
Definition ConcurrentLinearAllocator.h:628
@ RequireRangeCheck
Definition ConcurrentLinearAllocator.h:629
int32 SizeType
Definition ConcurrentLinearAllocator.h:626
Definition ContainerAllocationPolicies.h:1662
Definition ContainerAllocationPolicies.h:894
Definition ContainerAllocationPolicies.h:1383
void * Alloc(int size)
implementation
Definition PlayInEditorLoadingScope.h:8
CORE_API void OnInvalidConcurrentLinearArrayAllocatorNum(int32 NewNum, SIZE_T NumBytesPerElement)
Definition ConcurrentLinearAllocator.cpp:6
Definition ConcurrentLinearAllocator.h:38
static void * Malloc(SIZE_T Size, uint32 Alignment)
Definition ConcurrentLinearAllocator.h:43
static constexpr bool SupportsAlignment
Definition ConcurrentLinearAllocator.h:39
static constexpr bool UsesFMalloc
Definition ConcurrentLinearAllocator.h:40
static constexpr uint32 MaxAlignment
Definition ConcurrentLinearAllocator.h:41
static UE_FORCEINLINE_HINT void Free(void *Pointer, SIZE_T Size)
Definition ConcurrentLinearAllocator.h:54
Definition ConcurrentLinearAllocator.h:154
static constexpr const char * TagName
Definition ConcurrentLinearAllocator.h:159
static constexpr bool AllowOversizedBlocks
Definition ConcurrentLinearAllocator.h:156
static constexpr bool InlineBlockAllocation
Definition ConcurrentLinearAllocator.h:158
static constexpr bool RequiresAccurateSize
Definition ConcurrentLinearAllocator.h:157
static constexpr uint32 BlockSize
Definition ConcurrentLinearAllocator.h:155
static constexpr UE_FORCEINLINE_HINT bool IsPowerOfTwo(T Value)
Definition UnrealMathUtility.h:519
static FORCENOINLINE CORE_API void Free(void *Original)
Definition UnrealMemory.cpp:685
static UE_FORCEINLINE_HINT void * Memset(void *Dest, uint8 Char, SIZE_T Count)
Definition UnrealMemory.h:119
Definition ContainerAllocationPolicies.h:247
@ IsZeroConstruct
Definition ContainerAllocationPolicies.h:248
Definition ContainerAllocationPolicies.h:256
Definition UnrealTypeTraits.h:40
Definition ConcurrentLinearAllocator.h:267
unsigned int FetchSub(unsigned int N, std::memory_order Order)
Definition ConcurrentLinearAllocator.h:269
void Store(unsigned int N, std::memory_order Order)
Definition ConcurrentLinearAllocator.h:274
std::atomic_uint Value
Definition ConcurrentLinearAllocator.h:279
Definition ConcurrentLinearAllocator.h:249
void Store(unsigned int N, std::memory_order)
Definition ConcurrentLinearAllocator.h:258
unsigned int FetchSub(unsigned int N, std::memory_order)
Definition ConcurrentLinearAllocator.h:251
unsigned int Value
Definition ConcurrentLinearAllocator.h:263