Unreal Engine - The "EngineMisc" in the low level memory tracker could be tricky for you.
A quick share about my finding for the EngineMisc category under Unreal's LLM (Low level memory tracker, not the AI LLM).
If you ever wonder why this category always shows a high amount of memory, then this knowledge might help ๐.
First of first, what does this EngineMisc mean exactly? From the UE documentation:
Any low-level memory that is not tracked in any other category.
We can agree that the description is too broad and couldn't really help us.
After some codebase digging, I find the EngineMisc is actually measuring the scope of FEngineLoop::Tick():
void FEngineLoop::Tick()
{
SCOPED_NAMED_EVENT_TEXT("FEngineLoop::Tick", FrameColor);
SCOPE_STALL_COUNTER(FEngineLoop::Tick, 2.0);
#if !UE_BUILD_SHIPPING && !UE_BUILD_TEST && MALLOC_GT_HOOKS
FScopedSampleMallocChurn ChurnTracker;
#endif
// let the low level mem tracker pump once a frame to update states
LLM(FLowLevelMemTracker::Get().UpdateStatsPerFrame());
LLM_SCOPE(ELLMTag::EngineMisc);
// ..... continue FEngineLoop::Tick() logic ..... //
}Yikes! That is a huge range to investigate if you're doing memory optimizations.
Fortunately, the โTickโ function suggested that EngineMisc is tracking per-frame memory allocations. Especially those tickable objects.
Let me use the Lyra sample here, with the following test code:
void ULyraWeaponStateComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (APawn* Pawn = GetPawn<APawn>())
{
if (ULyraEquipmentManagerComponent* EquipmentManager = Pawn->FindComponentByClass<ULyraEquipmentManagerComponent>())
{
if (ULyraRangedWeaponInstance* CurrentWeapon = Cast<ULyraRangedWeaponInstance>(EquipmentManager->GetFirstInstanceOfType(ULyraRangedWeaponInstance::StaticClass())))
{
CurrentWeapon->Tick(DeltaTime);
}
}
}
MyLargeBuffer.Empty();
MyLargeBuffer.SetNum(9999999);
for (int32 i = 0; i < 100; i++)
{
MyLargeBuffer[i] = FMatrix();
}
}
Scary! Anything you added in your customized components would eventually be counted as EngineMisc.
To fix this, adding your own LLM tag/scope:
#include "HAL/LowLevelMemTracker.h"
#include "HAL/LowLevelMemStats.h"
DECLARE_LLM_MEMORY_STAT(TEXT("ULyraWeaponStateComponent::Tick"), STAT_LyraWeaponStateLLM, STATGROUP_LLMFULL);
DECLARE_LLM_MEMORY_STAT(TEXT("ULyraWeaponStateComponent::Tick"), STAT_LyraWeaponStateSummaryLLM, STATGROUP_LLM);
LLM_DEFINE_TAG(LyraWeaponLLM, NAME_None, NAME_None, GET_STATFNAME(STAT_LyraWeaponStateLLM), GET_STATFNAME(STAT_LyraWeaponStateSummaryLLM));
void ULyraWeaponStateComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
LLM_SCOPE_BYTAG(LyraWeaponLLM);
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (APawn* Pawn = GetPawn<APawn>())
{
if (ULyraEquipmentManagerComponent* EquipmentManager = Pawn->FindComponentByClass<ULyraEquipmentManagerComponent>())
{
if (ULyraRangedWeaponInstance* CurrentWeapon = Cast<ULyraRangedWeaponInstance>(EquipmentManager->GetFirstInstanceOfType(ULyraRangedWeaponInstance::StaticClass())))
{
CurrentWeapon->Tick(DeltaTime);
}
}
}
MyLargeBuffer.Empty();
MyLargeBuffer.SetNum(9999999);
for (int32 i = 0; i < 100; i++)
{
MyLargeBuffer[i] = FMatrix();
}
}
Ta, your memory usage won't just roughly show in EngineMisc.
In the fixed screenshot, you can see the EngineMisc dropped to 157.76MB after I added the custom tag.
Which means the LLM can also trace parent-child tags properly. And that is a good thing.
Summary
If you ever wonder why the โEngineMiscโ shows a high amount memory usage.
The reason is that category mainly traces the memory usage from tick functions.
But the official document is not clear enough.
If you join or take over a project that has a similar issue. Try to narrow down the LLM usage in the following areas:
UActorComponent::TickComponent()
AActor::TickActor()
Discussion