-
在虚幻中,我们可以通过FMemory向操作系统申请和释放内存,FMemory类似统一接口层,API非常类似c语言中的malloc和free,其底层有很多不同的内存管理方案可以选择,由于虚幻主要是靠c++编写,所以也重写了new和delete使得用户可以在高层直接使用FMemory来申请内存和初始化对象,这么做可以提高分配性能,减少内存碎片化,监控内存的分配,减少泄漏
-
不同平台所能支持的内存管理方案不同,所以虚幻提供了不同的分配器可供选择,在文件GenericPlatformMemory.h中提供了如下的宏,其中Ansi为标准的c内存分配器,Binned系统为虚幻自带的分配器:
/** Which allocator is being used */ enum EMemoryAllocatorToUse { Ansi, // Default C allocator Stomp, // Allocator to check for memory stomping TBB, // Thread Building Blocks malloc Jemalloc, // Linux/FreeBSD malloc Binned, // Older binned malloc Binned2, // Newer binned malloc Binned3, // Newer VM-based binned malloc, 64 bit only Platform, // Custom platform specific allocator Mimalloc, // mimalloc };
-
在UnrealMemory.cpp文件中,虚幻在FMemory_GCreateMalloc_ThreadUnsafe函数中创建了对应平台的全局内存分配器GMalloc,流程如下:
/** Helper function called on first allocation to create and initialize GMalloc */ static int FMemory_GCreateMalloc_ThreadUnsafe() { #if !PLATFORM_MAC FPlatformMemoryStats Stats = FPlatformMemory::GetStats(); uint64 ProgramSize = Stats.UsedPhysical; #endif GMalloc = FPlatformMemory::BaseAllocator(); ......
-
new和delete操作符都是在模块实现和注册时就被重写了,比如调用了new,从其调用栈可以看到,实际调用的是ModuleBoilerplate.h中的版本:


-
并且最终调用的内存分配器是TBB的FMalloc::Malloc函数,实际上虚幻里的智能指针、TArray、FString、TMap和UObject等都是类似的流程:

虚幻提供的常用内存剖析工具有LLM(Low Level Memory Tracker)、Memreport和MallocProfiler等,其中比较新的是LLM即低级内存跟踪器,
-
LLM是利用栈顶来标记当前作用域范围,将栈顶所在作用域范围内的FMemory所分配的内存统计到指定的标签下,最后结束时,再将整个栈所统计到的内存汇总到每个指定的标签下,未有关联标签的内存记到untagged标签下,这样就不会漏掉任何的分配内存
-
LowLevelMemTracker.h头文件中定义很多不同的标签,并给出了标签的起始范围,目前我们只关心项目标签:
/* * Enum values to be passed in to LLM_SCOPE() macro */ enum class ELLMTag : LLM_TAG_TYPE { #define LLM_ENUM(Enum,Str,Stat,Group,Parent) Enum, LLM_ENUM_GENERIC_TAGS(LLM_ENUM) #undef LLM_ENUM GenericTagCount, //------------------------------ // Platform tags PlatformTagStart = 100, PlatformTagEnd = 149, //------------------------------ // Project tags ProjectTagStart = 150, ProjectTagEnd = 255, // anything above this value is treated as an FName for a stat section };
-
启动后会在saved/profiling/llm/中输出类似LLM_Pid15256_2022.04.12-00.51.40_Windows.csv的csv文件,是调用FMemory的Malloc的内存分配情况,也可以输入按~建在控制台输入stat LLMFULL得到下面的结果:

-
启动后会在saved/profiling/llm/中输出类似LLMPlatform_Pid15256_2022.04.12-00.51.40_Windows.csv的csv文件,这是是从系统进行内存分配的情况,也可以输入按~建在控制台输入stat LLMPlatform得到下面的结果:

-
每个tag的详细介绍可以查询资料
-
我们可以自定义自己的tag在LLMFULL中进行统计展现,例子如下:
IMPLEMENT_PRIMARY_GAME_MODULE(FLessonMemoryModule, Lesson_Memory, "Lesson_Memory" ); DECLARE_LLM_MEMORY_STAT(TEXT("DanMemProf"), STAT_DanMemProfLLM, STATGROUP_LLMFULL); void FLessonMemoryModule::StartupModule(){ LLM(FLowLevelMemTracker::Get().RegisterProjectTag((int32)EMyLLMTag::DanMemProf, TEXT("DanMemProf"), GET_STATFNAME(STAT_DanMemProfLLM), NAME_None)); }
-
使用tag的例子:
void AMemoryActor::Tick(float DeltaTime) { { LLM_SCOPE(ELLMTag((int)(EMyLLMTag::DanMemProf))); Ptra = new(A); //delete Ptra; } }
-
输出的csv文件包含每个标记的列,以MB为单位显示当前值。默认每隔5秒记录一下,可以使用LLM.LLMWriteInterval在控制台变量更改,这里可以发现DanMemProf统计的内存出现明显泄漏:


