UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
LowLevelMemoryUtils.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
6
7#if ENABLE_LOW_LEVEL_MEM_TRACKER
8
9#include "Algo/BinarySearch.h"
10#include "Async/UniqueLock.h"
12#include "Containers/Map.h"
13#include "Containers/Set.h"
14#include "HAL/PlatformMutex.h"
15#include "Math/NumericLimits.h"
16#include "Templates/Tuple.h"
17#include <type_traits>
18
19#define LLM_PAGE_SIZE (16*1024)
20
21#if WITH_EDITOR && PLATFORM_64BITS
22// When cooking, the number of simultaneous allocations can reach the danger zone of tens of millions, and our margin*capacity calculation ~ 100*capacity will rise over MAX_uint32
24#else
25// Even in our 64 bit runtimes, the number of simultaneous allocations we have never gets over a few million, so we don't reach the danger zone of 100*capacity > MAX_uInt32
27#endif
28
29// Trivially movable types without destructors only; memcpy is used and destructors are not called
30template<typename T, typename SizeType=int32>
31class FLLMArray
32{
33public:
34 FLLMArray()
36 , Count(0)
37 , Capacity(StaticArrayCapacity)
38 , Allocator(nullptr)
39 {
40 }
41
43 {
44 Clear(true);
45 }
46
47 void SetAllocator(UE::LLMPrivate::FLLMAllocator* InAllocator)
48 {
50 }
51
52 SizeType Num() const
53 {
54 return Count;
55 }
56
57 void Clear(bool ReleaseMemory = false)
58 {
59 if (ReleaseMemory)
60 {
61 if (Array != StaticArray)
62 {
63 Allocator->Free(Array, Capacity * sizeof(T));
65 }
66 Capacity = StaticArrayCapacity;
67 }
68 Count = 0;
69 }
70
71 void Add(const T& Item)
72 {
73 if (Count == Capacity)
74 {
76 if (Capacity)
77 {
78 NewCapacity = Capacity + (Capacity / 2);
79 ensureMsgf(NewCapacity > Capacity, TEXT("Unsigned integer overflow."));
80 }
81 Reserve(NewCapacity);
82 }
83
84 Array[Count] = Item;
85 ++Count;
86 }
87
88 T RemoveLast()
89 {
90 LLMCheck(Count > 0);
91 --Count;
92 T Last = Array[Count];
93
94 return Last;
95 }
96
97 T& operator[](SizeType Index)
98 {
99 LLMCheck(Index >= 0 && Index < Count);
100 return Array[Index];
101 }
102
103 const T& operator[](SizeType Index) const
104 {
105 LLMCheck(Index >= 0 && Index < Count);
106 return Array[Index];
107 }
108
109 const T* GetData() const {
110 return Array;
111 }
112
113 T& GetLast()
114 {
115 LLMCheck(Count > 0);
116 return Array[Count - 1];
117 }
118
119 void Reserve(SizeType NewCapacity)
120 {
121 if (NewCapacity == Capacity)
122 {
123 return;
124 }
125
127 {
128 if (Array != StaticArray)
129 {
130 if (Count)
131 {
132 memcpy(StaticArray, Array, Count * sizeof(T));
133 }
134 Allocator->Free(Array, Capacity * sizeof(T));
135
137 Capacity = StaticArrayCapacity;
138 }
139 }
140 else
141 {
143
144 T* NewArray = (T*)Allocator->Alloc(NewCapacity * sizeof(T));
145
146 if (Count)
147 {
148 memcpy(NewArray, Array, Count * sizeof(T));
149 }
150 if (Array != StaticArray)
151 {
152 Allocator->Free(Array, Capacity * sizeof(T));
153 }
154
155 Array = NewArray;
156 Capacity = NewCapacity;
157 }
158 }
159
160 void operator=(const FLLMArray<T>& Other)
161 {
162 Clear();
163 Reserve(Other.Count);
164 memcpy(Array, Other.Array, Other.Count * sizeof(T));
165 Count = Other.Count;
166 }
167
168 void Trim()
169 {
170 // Trim if usage has dropped below 3/4 of the total capacity
171 if (Array != StaticArray && Count < (Capacity - (Capacity / 4)))
172 {
173 Reserve(Count);
174 }
175 }
176
177private:
178 T* Array;
179 SizeType Count;
180 SizeType Capacity;
181
182 UE::LLMPrivate::FLLMAllocator* Allocator;
183
184 static const int StaticArrayCapacity = 64; // because the default size is so large this actually saves memory
186
187 static const int ItemsPerPage = LLM_PAGE_SIZE / sizeof(T);
188 static const int DefaultCapacity = ItemsPerPage;
189};
190
191/*
192* hash map
193*/
194template<typename TKey, typename TValue1, typename TValue2, typename SizeType=int32>
195class LLMMap
196{
197public:
199
200 struct Values
201 {
202 TKey Key;
203 TValue1 Value1;
204 TValue2 Value2;
205 };
206
207 LLMMap()
209 {
210 }
211
212 ~LLMMap()
213 {
214 Clear();
215 }
216
217 void SetAllocator(UE::LLMPrivate::FLLMAllocator* InAllocator, SizeType InDefaultCapacity = DefaultCapacity)
218 {
220 {
221 Stripe.Allocator = InAllocator;
222
223 Stripe.Keys.SetAllocator(InAllocator);
224 Stripe.KeyHashes.SetAllocator(InAllocator);
225 Stripe.Values1.SetAllocator(InAllocator);
226 Stripe.Values2.SetAllocator(InAllocator);
227 Stripe.FreeKeyIndices.SetAllocator(InAllocator);
228
229 Stripe.Reserve(InDefaultCapacity / StripeCount);
230 }
231 }
232
233 void Clear()
234 {
236 {
238
239 Stripe.Keys.Clear(true);
240 Stripe.KeyHashes.Clear(true);
241 Stripe.Values1.Clear(true);
242 Stripe.Values2.Clear(true);
243 Stripe.FreeKeyIndices.Clear(true);
244
245 Stripe.Allocator->Free(Stripe.Map, Stripe.Capacity * sizeof(SizeType));
246 Stripe.Map = NULL;
247 Stripe.Count = 0;
248 Stripe.Capacity = 0;
249 }
250 }
251
252 // Add a value to this set.
253 // If this set already contains the value does nothing.
254 void Add(const TKey& Key, const TValue1& Value1, const TValue2& Value2)
255 {
256 SizeType KeyHash = Key.GetHashCode();
258
260
261 LLMCheck(Stripe.Map);
262
263 SizeType MapIndex = Stripe.GetMapIndex(Key, KeyHash);
264 SizeType KeyIndex = Stripe.Map[MapIndex];
265
266 if (KeyIndex != InvalidIndex)
267 {
268 static bool ShownWarning = false;
269 if (!ShownWarning)
270 {
271 FPlatformMisc::LowLevelOutputDebugString(TEXT("LLM WARNING: Replacing allocation in tracking map. Alloc/Free Mismatch.\n"));
272 ShownWarning = true;
273 }
274
275 Stripe.Values1[KeyIndex] = Value1;
276 Stripe.Values2[KeyIndex] = Value2;
277 }
278 else
279 {
280 // Be careful with the multiplication to avoid overflow, while still using only integer arithmetic for speed
281 // Margin/256U is the actual float we want to multiply by. The exact number doesn't matter, so use one order of operations
282 // when Capacity is low, and another when it is high
283 SizeType MaxCount = (Stripe.Capacity >= 256U * 256U ? (Stripe.Capacity / 256U) * Margin : (Margin * Stripe.Capacity) / 256U);
284 if (Stripe.Count >= MaxCount)
285 {
286 if (Stripe.Capacity * 2 < Stripe.Capacity)
287 {
288 // Trying to issue a check statement here will cause reentry into this function, use PLATFORM_BREAK directly instead
289 FPlatformMisc::LowLevelOutputDebugString(TEXT("LLM Error: Integer overflow in LLMap::Add, Capacity has reached its maximum size.\n"));
291 }
292 if (Stripe.Count > MaxCount)
293 {
294 // Trying to issue a check statement here will cause reentry into this function, use PLATFORM_BREAK directly instead
295 // This shouldn't happen, because Count is only incremented here, and Capacity is only changed here, and Margin does not change, so Count should equal MaxCount before it goes over it
296 FPlatformMisc::LowLevelOutputDebugString(TEXT("LLM Assertion failure: Count > MaxCount.\n"));
298 }
299 Stripe.Grow();
300 MapIndex = Stripe.GetMapIndex(Key, KeyHash);
301 }
302
303 if (Stripe.FreeKeyIndices.Num())
304 {
305 SizeType FreeIndex = Stripe.FreeKeyIndices.RemoveLast();
306 Stripe.Map[MapIndex] = FreeIndex;
307 Stripe.Keys[FreeIndex] = Key;
308 Stripe.KeyHashes[FreeIndex] = KeyHash;
309 Stripe.Values1[FreeIndex] = Value1;
310 Stripe.Values2[FreeIndex] = Value2;
311 }
312 else
313 {
314 Stripe.Map[MapIndex] = Stripe.Keys.Num();
315 Stripe.Keys.Add(Key);
316 Stripe.KeyHashes.Add(KeyHash);
317 Stripe.Values1.Add(Value1);
318 Stripe.Values2.Add(Value2);
319 }
320
321 ++Stripe.Count;
322 }
323 }
324
325 Values GetValue(const TKey& Key)
326 {
327 Values RetValues;
328 RetValues.Key = Find(Key, RetValues.Value1, RetValues.Value2);
329 LLMEnsure(RetValues.Key);
330 return RetValues;
331 }
332
333 TKey Find(const TKey& Key, TValue1& OutValue1, TValue2& OutValue2) const
334 {
335 SizeType KeyHash = Key.GetHashCode();
336 const StripeData& Stripe = MapStripes[GetStripeIndex(KeyHash)];
337
339
340 SizeType MapIndex = Stripe.GetMapIndex(Key, KeyHash);
341
342 SizeType KeyIndex = Stripe.Map[MapIndex];
343 if (KeyIndex == InvalidIndex)
344 {
345 return TKey();
346 }
347
348 OutValue1 = Stripe.Values1[KeyIndex];
349 OutValue2 = Stripe.Values2[KeyIndex];
350
351 return Stripe.Keys[KeyIndex];
352 }
353
354 bool Remove(const TKey& Key, Values& OutValues)
355 {
356 SizeType KeyHash = Key.GetHashCode();
358
360
361 LLMCheck(Stripe.Map);
362
363 SizeType MapIndex = Stripe.GetMapIndex(Key, KeyHash);
364 if (!Stripe.IsItemInUse(MapIndex))
365 {
366 return false;
367 }
368
369 SizeType KeyIndex = Stripe.Map[MapIndex];
370
371 OutValues.Key = Stripe.Keys[KeyIndex];
372 OutValues.Value1 = Stripe.Values1[KeyIndex];
373 OutValues.Value2 = Stripe.Values2[KeyIndex];
374
375 if (KeyIndex == Stripe.Keys.Num() - 1)
376 {
377 Stripe.Keys.RemoveLast();
378 Stripe.KeyHashes.RemoveLast();
379 Stripe.Values1.RemoveLast();
380 Stripe.Values2.RemoveLast();
381 }
382 else
383 {
384 Stripe.FreeKeyIndices.Add(KeyIndex);
385 }
386
387 // find first index in this array
388 SizeType IndexIter = MapIndex;
389 SizeType FirstIndex = MapIndex;
390 if (!IndexIter)
391 {
392 IndexIter = Stripe.Capacity;
393 }
394 --IndexIter;
395 while (Stripe.IsItemInUse(IndexIter))
396 {
397 FirstIndex = IndexIter;
398 if (!IndexIter)
399 {
400 IndexIter = Stripe.Capacity;
401 }
402 --IndexIter;
403 }
404
405 bool Found = false;
406 for (;;)
407 {
408 // find the last item in the array that can replace the item being removed
409 SizeType IndexIter2 = (MapIndex + 1) & (Stripe.Capacity - 1);
410
411 SizeType SwapIndex = InvalidIndex;
412 while (Stripe.IsItemInUse(IndexIter2))
413 {
414 SizeType SearchKeyIndex = Stripe.Map[IndexIter2];
415 const SizeType SearchHashCode = Stripe.KeyHashes[SearchKeyIndex];
416 const SizeType SearchInsertIndex = SearchHashCode & (Stripe.Capacity - 1);
417
418 if (Stripe.InRange(SearchInsertIndex, FirstIndex, MapIndex))
419 {
421 Found = true;
422 }
423
424 IndexIter2 = (IndexIter2 + 1) & (Stripe.Capacity - 1);
425 }
426
427 // swap the item
428 if (Found)
429 {
430 Stripe.Map[MapIndex] = Stripe.Map[SwapIndex];
431 MapIndex = SwapIndex;
432 Found = false;
433 }
434 else
435 {
436 break;
437 }
438 }
439
440 // remove the last item
441 Stripe.Map[MapIndex] = InvalidIndex;
442
443 --Stripe.Count;
444
445 return true;
446 }
447
448 SizeType Num() const
449 {
450 SizeType TotalCount = 0;
452 {
454 TotalCount += Stripe.Count;
455 }
456 return TotalCount;
457 }
458
459 bool HasKey(const TKey& Key)
460 {
461 SizeType KeyHash = Key.GetHashCode();
463
465
466 SizeType MapIndex = Stripe.GetMapIndex(Key, KeyHash);
467 return Stripe.IsItemInUse(MapIndex);
468 }
469
470 void Trim()
471 {
473 {
475
476 Stripe.Keys.Trim();
477 Stripe.KeyHashes.Trim();
478 Stripe.Values1.Trim();
479 Stripe.Values2.Trim();
480 Stripe.FreeKeyIndices.Trim();
481 }
482 }
483
484 struct FBaseIterator
485 {
486 public:
487 FBaseIterator& operator++()
488 {
489 ++MapIndex;
490 while (StripeIndex < StripeCount)
491 {
492 StripeData& Stripe = MapRef.MapStripes[StripeIndex];
493 while (MapIndex < Stripe.Capacity)
494 {
495 if (Stripe.IsItemInUse(MapIndex))
496 {
497 return *this;
498 }
499 MapIndex++;
500 }
501
502 ++StripeIndex;
503 MapIndex = 0;
504 }
505
506 return *this;
507 }
508
509
510 protected:
511 FBaseIterator(ThisType& InMap, bool bEnd)
512 : MapRef(InMap), MapIndex(0)
513 {
514 if (bEnd)
515 {
516 StripeIndex = StripeCount;
517 MapIndex = 0;
518 }
519 else
520 {
521 StripeIndex = 0;
522 MapIndex = -1;
523 ++(*this);
524 }
525 }
526
527 ThisType& MapRef;
529 SizeType MapIndex;
530 };
531
532 struct FIterator;
533 struct FTuple
534 {
535 const TKey& Key;
536 TValue1& Value1;
537 TValue2& Value2;
538
539 private:
541 :Key(InKey), Value1(InValue1), Value2(InValue2)
542 {
543 }
544
545 friend struct ThisType::FIterator;
546 };
547
548 struct FIterator : public FBaseIterator
549 {
550 FIterator(ThisType& InMap, bool bEnd)
551 : FBaseIterator(InMap, bEnd)
552 {
553 }
554
555 bool operator!=(const FIterator& Other) const
556 {
557 return FBaseIterator::StripeIndex != Other.FBaseIterator::StripeIndex ||
558 FBaseIterator::MapIndex != Other.FBaseIterator::MapIndex;
559 }
560
561 FTuple operator*() const
562 {
563 ThisType& LocalMap = FBaseIterator::MapRef;
564 StripeData& Stripe = LocalMap.MapStripes[FBaseIterator::StripeIndex];
565 SizeType KeyIndex = Stripe.Map[FBaseIterator::MapIndex];
566 return FTuple(Stripe.Keys[KeyIndex], Stripe.Values1[KeyIndex], Stripe.Values2[KeyIndex]);
567 }
568 };
569
570 struct FConstIterator;
571 struct FConstTuple
572 {
573 const TKey& Key;
574 const TValue1& Value1;
575 const TValue2& Value2;
576
577 private:
578 FConstTuple(const TKey& InKey, const TValue1& InValue1, const TValue2& InValue2)
579 :Key(InKey), Value1(InValue1), Value2(InValue2)
580 {
581 }
582
583 friend struct ThisType::FConstIterator;
584 };
585
586 struct FConstIterator : public FBaseIterator
587 {
588 FConstIterator(const ThisType& InMap, bool bEnd)
589 : FBaseIterator(const_cast<ThisType&>(InMap), bEnd)
590 {
591 }
592
593 bool operator!=(const FConstIterator& Other) const
594 {
595 return FBaseIterator::StripeIndex != Other.FBaseIterator::StripeIndex ||
596 FBaseIterator::MapIndex != Other.FBaseIterator::MapIndex;
597 }
598
599 FConstTuple operator*() const
600 {
601 ThisType& LocalMap = FBaseIterator::MapRef;
602 StripeData& Stripe = LocalMap.MapStripes[FBaseIterator::StripeIndex];
603 SizeType KeyIndex = Stripe.Map[FBaseIterator::MapIndex];
604 return FConstTuple(Stripe.Keys[KeyIndex], Stripe.Values1[KeyIndex], Stripe.Values2[KeyIndex]);
605 }
606 };
607
608 FIterator begin()
609 {
611 return FIterator(*this, false);
612 }
613
614 FIterator end()
615 {
616 return FIterator(*this, true);
617 }
618
619 FConstIterator begin() const
620 {
622 return FConstIterator(*this, false);
623 }
624
625 FConstIterator end() const
626 {
627 return FConstIterator(*this, true);
628 }
629
630 void LockAll()
631 {
632 // Locking all stripes is required to create an iterator
633 AllStripesMutex.Lock();
634 LLMCheck(bAllStripesLocked == false);
635 bAllStripesLocked = true;
636
638 {
639 Stripe.Mutex.Lock();
640 }
641 }
642
643 void UnlockAll()
644 {
646 {
647 Stripe.Mutex.Unlock();
648 }
649
651 bAllStripesLocked = false;
652 AllStripesMutex.Unlock();
653 }
654
655 // data
656private:
657 static const int32 StripeCountLog2 = 4; // 16 stripes
658 enum { StripeCount = 1 << StripeCountLog2 };
659 enum { DefaultCapacity = 1024 * 1024 };
660 enum { InvalidIndex = -1 };
661 static const SizeType Margin = (30 * 256) / 100;
662
663 static inline uint32 GetStripeIndex(uint32 KeyHash)
664 {
665 // FMath::Max prevents "shift too large" compile error in otherwise unreachable code when StripeCount == 1
666 return (StripeCount == 1) ? 0 : KeyHash >> (32 - FMath::Max(StripeCountLog2, 1));
667 }
668
669 static inline uint32 GetStripeIndex(uint64 KeyHash)
670 {
671 // FMath::Max prevents "shift too large" compile error in otherwise unreachable code when StripeCount == 1
672 return static_cast<uint32>((StripeCount == 1) ? 0 : KeyHash >> (64 - FMath::Max(StripeCountLog2, 1)));
673 }
674
675 struct StripeData
676 {
677 StripeData()
678 : Allocator(nullptr)
679 , Map(nullptr)
680 , Count(0)
681 , Capacity(0)
683 , IterAcc(0)
684 , IterCount(0)
685#endif
686 {}
687
688 void Reserve(SizeType NewCapacity)
689 {
690 NewCapacity = FMath::Max(NewCapacity, (SizeType)1);
691 NewCapacity = static_cast<SizeType>(FPlatformMath::RoundUpToPowerOfTwo64(static_cast<uint64>(NewCapacity)));
692
693 // keep a copy of the old map
694 SizeType* OldMap = Map;
695 SizeType OldCapacity = Capacity;
696
698
699 // allocate the new table
700 Capacity = NewCapacity;
701 Map = (SizeType*)Allocator->Alloc(NewCapacity * sizeof(SizeType));
702
703 for (SizeType Index = 0; Index < NewCapacity; ++Index)
704 Map[Index] = InvalidIndex;
705
706 // copy the values from the old to the new table
707 SizeType* OldItem = OldMap;
708 for (SizeType Index = 0; Index < OldCapacity; ++Index, ++OldItem)
709 {
710 SizeType KeyIndex = *OldItem;
711
712 if (KeyIndex != InvalidIndex)
713 {
714 SizeType MapIndex = GetMapIndex(Keys[KeyIndex], KeyHashes[KeyIndex]);
715 Map[MapIndex] = KeyIndex;
716 }
717 }
718
719 Allocator->Free(OldMap, OldCapacity * sizeof(SizeType));
720 }
721
722 bool IsItemInUse(SizeType MapIndex) const
723 {
724 return Map[MapIndex] != InvalidIndex;
725 }
726
727 SizeType GetMapIndex(const TKey& Key, SizeType Hash) const
728 {
729 SizeType Mask = Capacity - 1;
730 SizeType MapIndex = Hash & Mask;
731 SizeType KeyIndex = Map[MapIndex];
732
733 while (KeyIndex != InvalidIndex && !(Keys[KeyIndex] == Key))
734 {
735 MapIndex = (MapIndex + 1) & Mask;
736 KeyIndex = Map[MapIndex];
737#ifdef PROFILE_LLMMAP
738 ++IterAcc;
739#endif
740 }
741
742#ifdef PROFILE_LLMMAP
743 ++IterCount;
744 double Average = IterAcc / (double)IterCount;
745 if (Average > 2.0)
746 {
747 static double LastWriteTime = 0.0;
748 double Now = FPlatformTime::Seconds();
749 if (Now - LastWriteTime > 5)
750 {
751 LastWriteTime = Now;
752 UE_LOG(LogStats, Log, TEXT("WARNING: LLMMap average: %f\n"), (float)Average);
753 }
754 }
755#endif
756 return MapIndex;
757 }
758
759 // Increase the capacity of the map
760 void Grow()
761 {
762 SizeType NewCapacity = Capacity ? 2 * Capacity : DefaultCapacity / StripeCount;
763 Reserve(NewCapacity);
764 }
765
766 static bool InRange(
767 const SizeType Index,
768 const SizeType StartIndex,
769 const SizeType EndIndex)
770 {
771 return (StartIndex <= EndIndex) ?
772 Index >= StartIndex && Index <= EndIndex :
773 Index >= StartIndex || Index <= EndIndex;
774 }
775
777
778 UE::LLMPrivate::FLLMAllocator* Allocator;
779
780 SizeType* Map;
781 SizeType Count;
782 SizeType Capacity;
783
784 // all these arrays must be kept in sync and are accessed by MapIndex
789
791
792#ifdef PROFILE_LLMMAP
793 mutable int64 IterAcc;
794 mutable int64 IterCount;
795#endif
796 };
797
798 // We divide map items into multiple stripes by the high bits of the hash key, to reduce lock contention.
799 // Each stripe has its own critical section lock, and given even distribution of hash codes (due to the
800 // mix function applied to the key), multiple threads will usually end up with their own lock, and not
801 // need to wait on each other.
802 StripeData MapStripes[StripeCount];
804 std::atomic_bool bAllStripesLocked;
805};
806
807// Pointer key for hash map
808struct PointerKey
809{
810 explicit PointerKey() = default;
811 explicit PointerKey(const void* InPointer, uint16 ExtraData=0)
812 : Pointer((UPTRINT(InPointer) << 16) | UPTRINT(ExtraData))
813 {
814 static_assert(sizeof(InPointer) == 8, "LLM only supports 64 bit platforms");
815 LLMEnsure(UPTRINT(InPointer) && UPTRINT(InPointer) <= 0x0000'ffff'ffff'ffff);
816 }
818 {
820 }
821 bool operator==(const PointerKey& other) const { return GetPointer() == other.GetPointer(); }
822 explicit operator bool () const { return !!Pointer; }
823 void* GetPointer() const { return (void*)(Pointer >> 16); };
824 uint64 GetExtraData() const { return uint64(Pointer & 0xffff); }
825
826private:
827 UPTRINT Pointer = 0;
828
829 template <int HashSize, int PointerSize>
831 {
832 static_assert(HashSize == 0 && PointerSize == 0, "Converting void* to a LLMNumAllocsType - sized hashkey is not implemented for the current sizes.");
833 return (LLMNumAllocsType)(UPTRINT)GetPointer();
834 }
835
836 template <>
838 {
839 // 64 bit pointer to 64 bit hash
840 uint64 Key = (uint64)GetPointer();
841 Key = (~Key) + (Key << 21);
842 Key = Key ^ (Key >> 24);
843 Key = Key * 265;
844 Key = Key ^ (Key >> 14);
845 Key = Key * 21;
846 Key = Key ^ (Key >> 28);
847 Key = Key + (Key << 31);
848 return (LLMNumAllocsType)Key;
849 }
850
851 template <>
853 {
854 // 64 bit pointer to 32 bit hash
855 uint64 Key = (uint64)GetPointer();
856 Key = (~Key) + (Key << 21);
857 Key = Key ^ (Key >> 24);
858 Key = Key * 265;
859 Key = Key ^ (Key >> 14);
860 Key = Key * 21;
861 Key = Key ^ (Key >> 28);
862 Key = Key + (Key << 31);
863 return (LLMNumAllocsType)Key;
864 }
865
866 template <>
868 {
869 // 32 bit pointer to 32 bit Hash
870 uint64 Key = (uint64)GetPointer();
871 Key = (~Key) + (Key << 18);
872 Key = Key ^ (Key >> 31);
873 Key = Key * 21;
874 Key = Key ^ (Key >> 11);
875 Key = Key + (Key << 6);
876 Key = Key ^ (Key >> 22);
877 return (LLMNumAllocsType)Key;
878 }
879};
880
881namespace UE::Core::Private
882{
883 [[noreturn]] CORE_API void OnInvalidLLMAllocatorNum(int32 IndexSize, int64 NewNum, SIZE_T NumBytesPerElement);
884}
885
890template<int IndexSize>
892{
893public:
894 using SizeType = typename TBitsToSizeType<IndexSize>::Type;
895
896private:
897 using USizeType = std::make_unsigned_t<SizeType>;
898
899public:
900 enum { NeedsElementType = false };
901 enum { RequireRangeCheck = true };
902
903 class ForAnyElementType
904 {
905 template <int>
906 friend class TSizedLLMAllocator;
907
908 public:
910 ForAnyElementType()
911 : Data(nullptr)
912 , Size(0)
913 {}
914
921 template <typename OtherAllocator>
922 FORCEINLINE void MoveToEmptyFromOtherAllocator(typename OtherAllocator::ForAnyElementType& Other)
923 {
924 checkSlow((void*)this != (void*)&Other);
925
926 if (Data)
927 {
928 UE::LLMPrivate::FLLMAllocator::Get()->Free(Data, Size);
929 }
930
931 Data = Other.Data;
932 Size = Other.Size;
933 Other.Data = nullptr;
934 Other.Size = 0;
935 }
936
944 FORCEINLINE void MoveToEmpty(ForAnyElementType& Other)
945 {
947 }
948
950 FORCEINLINE ~ForAnyElementType()
951 {
952 if (Data)
953 {
954 UE::LLMPrivate::FLLMAllocator::Get()->Free(Data, Size);
955 }
956 }
957
958 FORCEINLINE void* GetAllocation() const
959 {
960 return Data;
961 }
962
963 FORCEINLINE void ResizeAllocation(SizeType CurrentNum, SizeType NewMax, SIZE_T NumBytesPerElement)
964 {
965 // Avoid calling FMemory::Realloc( nullptr, 0 ) as ANSI C mandates returning a valid pointer which is not what we want.
966 if (Data || NewMax)
967 {
968 static_assert(sizeof(SizeType) <= sizeof(SIZE_T), "SIZE_T is expected to handle all possible sizes");
969
970 // Check for under/overflow
972 if constexpr (sizeof(SizeType) == sizeof(SIZE_T))
973 {
974 bInvalidResize = bInvalidResize || (SIZE_T)(USizeType)NewMax > (SIZE_T)TNumericLimits<SizeType>::Max() / NumBytesPerElement;
975 }
977 {
978 UE::Core::Private::OnInvalidLLMAllocatorNum(IndexSize, NewMax, NumBytesPerElement);
979 }
980
981 //checkSlow(((uint64)NewMax*(uint64)ElementTypeInfo.GetSize() < (uint64)INT_MAX));
982 size_t NewSize = NewMax * NumBytesPerElement;
983 Data = UE::LLMPrivate::FLLMAllocator::Get()->Realloc(Data, Size, NewSize);
984 Size = NewSize;
985 }
986 }
987 FORCEINLINE SizeType CalculateSlackReserve(SizeType NewMax, SIZE_T NumBytesPerElement) const
988 {
989 return DefaultCalculateSlackReserve(NewMax, NumBytesPerElement, true);
990 }
991 FORCEINLINE SizeType CalculateSlackShrink(SizeType NewMax, SizeType CurrentMax, SIZE_T NumBytesPerElement) const
992 {
993 return DefaultCalculateSlackShrink(NewMax, CurrentMax, NumBytesPerElement, true);
994 }
995 FORCEINLINE SizeType CalculateSlackGrow(SizeType NewMax, SizeType CurrentMax, SIZE_T NumBytesPerElement) const
996 {
997 return DefaultCalculateSlackGrow(NewMax, CurrentMax, NumBytesPerElement, true);
998 }
999
1000 SIZE_T GetAllocatedSize(SizeType CurrentMax, SIZE_T NumBytesPerElement) const
1001 {
1002 return CurrentMax * NumBytesPerElement;
1003 }
1004
1005 bool HasAllocation() const
1006 {
1007 return !!Data;
1008 }
1009
1010 SizeType GetInitialCapacity() const
1011 {
1012 return 0;
1013 }
1014
1015 private:
1016 ForAnyElementType(const ForAnyElementType&);
1017 ForAnyElementType& operator=(const ForAnyElementType&);
1018
1020 void* Data;
1022 size_t Size;
1023 };
1024
1025 template<typename ElementType>
1026 class ForElementType : public ForAnyElementType
1027 {
1028 public:
1029
1031 ForElementType()
1032 {}
1033
1034 FORCEINLINE ElementType* GetAllocation() const
1035 {
1036 return (ElementType*)ForAnyElementType::GetAllocation();
1037 }
1038 };
1039};
1040
1041// Define the ResizeAllocation functions with the regular allocators as exported to avoid bloat
1042extern template CORE_API FORCENOINLINE void TSizedLLMAllocator<32>::ForAnyElementType::ResizeAllocation(SizeType CurrentNum, SizeType NewMax, SIZE_T NumBytesPerElement);
1043extern template CORE_API FORCENOINLINE void TSizedLLMAllocator<64>::ForAnyElementType::ResizeAllocation(SizeType CurrentNum, SizeType NewMax, SIZE_T NumBytesPerElement);
1044
1045// The standard container-specific allocators based on TSizedLLMAllocator; these are copied from ContainerAllocationPolicies.h
1048
1052
1053template<typename KeyType, typename ValueType>
1054class TLLMMap : public TMap<KeyType, ValueType, FDefaultSetLLMAllocator>
1055{};
1056
1057template<typename KeyType>
1058class TLLMSet : public TSet<KeyType, DefaultKeyFuncs<KeyType>, FDefaultSetLLMAllocator>
1059{};
1060
1061
1062
1063// Some algorithms used by LLM that require scratch-space internal allocation. To avoid polluting
1064// the Algo namespace with an Allocator parameter, we've copied (or privately implemented the nonexisting ones) those
1065// algorithms here
1066namespace LLMAlgoImpl
1067{
1069 {
1070 RootToLeaf,
1072 };
1073
1074 template <typename T, typename GetEdgesType, typename SizeType>
1075 void TopologicalSort(T* VertexData, SizeType NumVertices, GetEdgesType GetEdges, ETopologicalSortOrder SortOrder);
1076}
1077
1078namespace LLMAlgo
1079{
1091 template <typename RangeType, typename GetEdgesType>
1092 void TopologicalSortRootToLeaf(RangeType&& Range, GetEdgesType GetEdges)
1093 {
1094 TopologicalSort(GetData(Range), GetNum(Range), GetEdges, LLMAlgoImpl::ETopologicalSortOrder::RootToLeaf);
1095 }
1096
1108 template <typename RangeType, typename GetEdgesType>
1109 void TopologicalSortLeafToRoot(RangeType&& Range, GetEdgesType GetEdges)
1110 {
1111 using namespace LLMAlgoImpl;
1112 TopologicalSort(GetData(Range), GetNum(Range), GetEdges, ETopologicalSortOrder::LeafToRoot);
1113 }
1114}
1115
1116namespace LLMAlgoImpl
1117{
1118
1132 template <typename T, typename GetEdgesType, typename SizeType>
1133 void TopologicalSort(T* VertexData, SizeType NumVertices, GetEdgesType GetEdges, ETopologicalSortOrder SortOrder)
1134 {
1135 if (NumVertices == 0)
1136 {
1137 return;
1138 }
1139 LLMCheckf(NumVertices > 0, TEXT("Invalid number of vertices"));
1140
1141 // In our traversal, we write vertices LeafToRoot. To make a stable sort, we need to iterate the input list from LeafToRoot as well.
1143 if (SortOrder == ETopologicalSortOrder::RootToLeaf)
1144 {
1145 TraversalStart = NumVertices - 1;
1146 TraversalEnd = -1;
1147 TraversalDir = -1;
1148 }
1149 else
1150 {
1151 TraversalStart = 0;
1152 TraversalEnd = NumVertices;
1153 TraversalDir = 1;
1154 }
1155
1157 LeafToRootOrder.Reserve(NumVertices);
1158
1159 {
1160 constexpr SizeType ExpectedMaxNumEdges = 16;
1161 struct FVisitData
1162 {
1163 SizeType Vertex;
1164 SizeType NextEdge;
1165 SizeType EdgeStart;
1166 };
1167
1169 Stack.Reserve(NumVertices);
1170
1172 EdgeBuffer.AddUninitialized(NumVertices);
1173 SizeType* EdgeBufferData = EdgeBuffer.GetData();
1174
1177
1179 Visited.AddDefaulted(NumVertices);
1180
1181 auto PushVertex = [&EdgesOnStack, &Stack, &GetEdges, &Visited, EdgeBufferData, NumVertices](SizeType Vertex)
1182 {
1183 Visited[Vertex] = true;
1184 Stack.Add(FVisitData{ Vertex, EdgesOnStack.Num(), EdgesOnStack.Num() });
1185 SizeType NumEdges = static_cast<SizeType>(-1);
1186 GetEdges(Vertex, EdgeBufferData, NumEdges);
1187 LLMCheckf(0 <= NumEdges && NumEdges <= NumVertices, TEXT("GetEdges function passed into TopologicalSort did not write OutNumEdges, or wrote an invalid value"));
1188 EdgesOnStack.Append(EdgeBufferData, NumEdges);
1189 };
1190
1192 {
1193 if (Visited[RootVertex])
1194 {
1195 continue;
1196 }
1197
1199 while (Stack.Num() > 0)
1200 {
1201 FVisitData& VisitData = Stack.Last();
1202 bool bPushed = false;
1203 SizeType NumEdgesOnStack = EdgesOnStack.Num();
1204 for (; VisitData.NextEdge < NumEdgesOnStack; ++VisitData.NextEdge)
1205 {
1206 SizeType TargetVertex = EdgesOnStack[VisitData.NextEdge];
1207 if (!Visited[TargetVertex])
1208 {
1209 ++VisitData.NextEdge;
1211 bPushed = true;
1212 break;
1213 }
1214 }
1215 if (!bPushed)
1216 {
1217 LeafToRootOrder.Add(VisitData.Vertex);
1218 EdgesOnStack.SetNum(VisitData.EdgeStart, EAllowShrinking::No);
1219 Stack.Pop(EAllowShrinking::No);
1220 }
1221 }
1222 }
1223 }
1224 LLMCheck(LeafToRootOrder.Num() == NumVertices); // This could only fail due to an internal logic error; all vertices should have been visited and added
1225
1227 Original.Reserve(NumVertices);
1228 for (SizeType n = 0; n < NumVertices; ++n)
1229 {
1230 Original.Add(MoveTemp(VertexData[n]));
1231 }
1232
1233 for (SizeType LeafToRootIndex = 0; LeafToRootIndex < NumVertices; ++LeafToRootIndex)
1234 {
1235 SizeType WriteIndex = TraversalStart + TraversalDir * LeafToRootIndex;
1236 SizeType ReadIndex = LeafToRootOrder[LeafToRootIndex];
1237 VertexData[WriteIndex] = MoveTemp(Original[ReadIndex]);
1238 }
1239 }
1240}
1241
1242#endif // #if ENABLE_LOW_LEVEL_MEM_TRACKER
1243
#define NULL
Definition oodle2base.h:134
constexpr T AlignArbitrary(T Val, uint64 Alignment)
Definition AlignmentTemplates.h:66
#define PLATFORM_BREAK()
Definition AndroidPlatformMisc.h:17
#define FORCENOINLINE
Definition AndroidPlatform.h:142
#define FORCEINLINE
Definition AndroidPlatform.h:140
#define checkSlow(expr)
Definition AssertionMacros.h:332
#define ensureMsgf( InExpression, InFormat,...)
Definition AssertionMacros.h:465
UE_FORCEINLINE_HINT FLinearColor operator*(float Scalar, const FLinearColor &Color)
Definition Color.h:473
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 TEXT(x)
Definition Platform.h:1272
FPlatformTypes::SIZE_T SIZE_T
An unsigned integer the same size as a pointer, the same as UPTRINT.
Definition Platform.h:1150
FPlatformTypes::int64 int64
A 64-bit signed integer.
Definition Platform.h:1127
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
FPlatformTypes::UPTRINT UPTRINT
An unsigned integer the same size as a pointer.
Definition Platform.h:1146
#define UNLIKELY(x)
Definition Platform.h:857
FPlatformTypes::uint64 uint64
A 64-bit unsigned integer.
Definition Platform.h:1117
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
UE_FORCEINLINE_HINT bool operator!=(const FIndexedPointer &Other) const
Definition LockFreeList.h:76
#define UE_LOG(CategoryName, Verbosity, Format,...)
Definition LogMacros.h:270
@ Num
Definition MetalRHIPrivate.h:234
@ Vertex
Definition MetalRHIPrivate.h:223
const bool
Definition NetworkReplayStreaming.h:178
#define MAX_int32
Definition NumericLimits.h:25
void * GetAllocation(void *Target, uint32 Size, uint32 Offset, uint32 Alignment=16)
Definition OpenGLBuffer.cpp:57
auto GetNum(const TStringConversion< Converter, DefaultConversionSize > &Conversion) -> decltype(Conversion.Length())
Definition StringConv.h:808
auto GetData(const TStringConversion< Converter, DefaultConversionSize > &Conversion) -> decltype(Conversion.Get())
Definition StringConv.h:802
UE_INTRINSIC_CAST UE_REWRITE constexpr std::remove_reference_t< T > && MoveTemp(T &&Obj) noexcept
Definition UnrealTemplate.h:520
uint32 Size
Definition VulkanMemory.cpp:4034
memcpy(InputBufferBase, BinkBlocksData, BinkBlocksSize)
uint16_t uint16
Definition binka_ue_file_header.h:7
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition Array.h:670
UE_FORCEINLINE_HINT SizeType AddUninitialized()
Definition Array.h:1664
UE_FORCEINLINE_HINT void Reserve(SizeType Number)
Definition Array.h:3016
Definition UnrealString.h.inl:34
Definition ContainerAllocationPolicies.h:1662
Definition ContainerAllocationPolicies.h:894
Definition ContainerAllocationPolicies.h:1383
Definition UniqueLock.h:20
UE_REWRITE bool TopologicalSort(RangeType &&UniqueRange, GetElementDependenciesType GetElementDependencies, ETopologicalSort Flags=ETopologicalSort::None)
Definition TopologicalSort.h:31
GeometryCollection::Facades::FMuscleActivationData Data
Definition MuscleActivationConstraints.h:15
T::FDataType GetValue(const UBlackboardComponent &Blackboard, const FName &Name, FBlackboard::FKey &InOutCachedKey, const typename T::FDataType &DefaultValue)
Definition ValueOrBBKey.h:51
SIZE_T GetAllocatedSize(const T &Value)
Definition ManagedArray.h:93
UE::FRecursiveMutex Mutex
Definition MeshPaintVirtualTexture.cpp:164
bool operator==(const FCachedAssetKey &A, const FCachedAssetKey &B)
Definition AssetDataMap.h:501
implementation
Definition PlayInEditorLoadingScope.h:8
FORCEINLINE FStridedReferenceIterator begin(FStridedReferenceView View)
Definition FastReferenceCollector.h:490
FORCEINLINE FStridedReferenceIterator end(FStridedReferenceView View)
Definition FastReferenceCollector.h:491
FPThreadsRecursiveMutex FPlatformRecursiveMutex
Definition AndroidPlatformMutex.h:12
TSetG< ElementType, KeyType, TDefaultHashTraits< ElementType >, CHeapRawAllocator > TSet
Definition Set.h:87
@ false
Definition radaudio_common.h:23
U16 Index
Definition radfft.cpp:71
static double Seconds()
Definition AndroidPlatformTime.h:20
static CORE_API void LowLevelOutputDebugString(const TCHAR *Message)
Definition GenericPlatformMisc.cpp:935
Definition UnrealMathUtility.h:270
Definition ContainerAllocationPolicies.h:605
Definition NumericLimits.h:41