本文共 3518 字,大约阅读时间需要 11 分钟。
LLM(Low Level Memory Tracker) 是从 4.18 开始引入的新的内存统计工具,比 memreport 统计数据更加详细精确,但又不会像 MallocProfiler 那样有很大的本身开销
相关代码实现在
Engine\Source\Runtime\Core\Public\HAL\LowLevelMemTracker.h Engine\Source\Runtime\Core\Private\HAL\LowLevelMemTracker.cpp
在 Development 和 Debug 下,LLM 相关的代码是默认编译的,在 Test 模式下,需要在 YourGame.Target.cs 文件中增加宏定义
ALLOW_LOW_LEVEL_MEM_TRACKER_IN_TEST=1
才会编译相关代码,Shipping 模式下不会编译
在编译时打开 LLM 后,要想在运行时使用 LLM 的功能,还要加上启动命令行
-LLM //运行时打开 LLM 统计-LLMCSV //将内存统计信息输出到 CSV 文件中,CSV 文件保存在 Saved\Profiling\LLM 目录下
如果想在运行时默认生效,可以在 Target.cs 文件中增加宏定义 LLM_AUTO_ENABLE=1
命令行 -LLMTAGSETS,用来指示资源统计分类是按类别来分,还是按具体资源来分,不过要使用这个功能需要在编译时在代码中打开LLM_ALLOW_ASSETS_TAGS宏(这个宏目前不能在 Target.cs 文件添加)
-LLMTAGSETS=Assets:按资源分类统计-LLMTAGSETS=AssetClasses:按资源类别分类统计
运行时开关 LLM 的处理见 FLowLevelMemTracker::ProcessCommandLine 函数
运行时,可以通过以下命令行来显示当前的统计信息
stat LLM: 显示 Default Tracker 分组的简要内存统计信息
stat LLMFULL: 显示 Default Tracker 分组的所有内存统计信息
stat LLMOveread: 显示 LLM 本身使用的内存信息
stat LLMPlatform: 显示 Platform Tracker 分组的内存统计信息
LLM.LLMWriteInterval: 修改 LLM 的内存信息写入 csv 文件的频率,默认 5 秒写一行
在已经装好版本的手机上,如果想要启动 LLM,可以通过 push UE4CommandLine.txt 到手机上动态改变游戏启动参数
新建一个 UE4CommandLine.txt,内容如下
../../../YourProject/YourProject.uproject -LLM -LLMCSV
新建一个 PushCommandLine.bat 并执行,内容如下
%ANDROID_HOME%\platform-tools\adb.exe shell mkdir -p /sdcard/UE4Game/YourProject%ANDROID_HOME%\platform-tools\adb.exe push UE4CommandLine.txt /sdcard/UE4Game/YourProject/UE4CommandLine.txt
IOS 需要将 ue4commandline.txt 通过文件共享复制到 Documents 目录下,注意这里的文件名是全小写
指定 -LLMCSV 命令行后,会得到两个 csv 文件,
一个是 LLM_ 前缀,LLM 主要关注 FMalloc 内存的消费者,主要就是引擎内各个部分的内存开销
另一个是 LLMPlatform_ 前缀,LLMPlatform 只关注系统内存的消费者,主要有几大类: ProgramSize, FMalloc, LLMOverhead。
注意,当同时指定 -LLMTAGSETS 命令行后,CSV 文件会错乱,因为写 CSV 文件时预留了第一行标题栏内容的缓冲区,当打开 LLMTAGSETS 之后,因为每个资源或资源类很多,会导致标题栏内容溢出,覆盖了后面的数据区域,所以这里需要自己按需求修改一下。
引擎(包括游戏代码)的每次内存分配都会被指定一个标记值,用于标识其所属的类别。也就是说,所有内存仅被跟踪一次,没有任何内存错过或被计算两次。所有类别的总计会累计到游戏所用的总内存。
除了以上的分类标签外,还可以添加自定义的标签,主要有两种方式
修改 LowLevelMemTrakcer.h 文件在 LLM_ENUM_GENERIC_TAGS按照同样格式添加
按同样格式添加一个新标签定义另外一种是通过调用RegisterPlatformTag/RegisterProjectTag,主要参数如下:
Tag: 标签枚举,不要和其它地方重复了Name签名,显示在 CSV 文件的标题栏中StatName: 与这个标签相关的 Stat 统计,在游戏中通过 stat 命令来显示,使用 DECLARE_LLM_MEMORY_STAT宏进行定义,可以为空(NAME_None)SummaryStatName: 汇总 Stat 统计,可以将多个 LLM STAT 汇总在一起,可以为空(NAME_None)ParentTag: 父级标签枚举值,父级标签对应的内存值是所有子级标签的和,级数最多两级
DECLARE_LLM_MEMORY_STAT(TEXT("Test"), STAT_TestLLM, STATGROUP_LLMFULL);enum class EMyLLMTag{ TestTag = ELLMTag::ProjectTagStart + 20,}void Init(){ LLM(FLowLevelMemTracker::Get().RegisterProjectTag((int32)EMyLLMTag::TestTag, TEXT("Test"), GET_STATFNAME(STAT_TestLLM), NAME_None));}void TestFunc(){ LLM_SCOPE(EMyLLMTag::TestTag);}
要添加新的标记,需要执行以下步骤:
将值添加到 LowLevelMemTracker.h 中的 ELLMTag 列举类型。
将相应的元素添加到 LowLevelMemTracker.cpp 中的 ELLMTagNames数组中。
使用 LLM_SCOPE宏将标记范围添加到代码中。
如果范围是特定于平台的,则以相同的方式添加到特定于平台的LLM文件中的列举类型,例如,PlayStation 4的 PS4LLM.cpp和PS4LLM.h
LLM的作用原理是维护由指针索引的所有分配的映射。该映射当前包含每个分配的大小及其指定的标记。游戏一次可有多达400万个实时分配,因此内存开销必须尽可能少。当前实现中每个分配使用21个字节:
分配 |
大小 |
指针 |
8个字节 |
指针散列键 |
4个字节 |
大小 |
4个字节 |
标记 |
1个字节 |
散列图索引 |
4个字节 |
使用 OnLowLevelAlloc函数跟踪分配时,标记堆栈顶部的标记将成为当前标记,存储在分配映射中,以该指针作为其关键帧。
为了避免争用,在单独的 FLLMThreadState类实例中跟踪每个标记的帧增量。在帧结束时,这些增量将相加并发布到统计信息系统和 .CSV 文件。
标记使用范围宏进行应用。以下是两个主要的宏:
LLM_SCOPE(Tag)LLM_PLATFORM_SCOPE(Tag)
这两个宏分别设置了默认跟踪器和平台跟踪器的当前范围。这些范围有平台相关版本,例如
LLM_SCOPE_PS4(Tag)
,它使用特定于平台的标记列举类型。使用统计信息的范围宏(例如
LLM_SCOPED_TAG_WITH_STAT
)目前被视为已弃用,不应被使用。
LLM内部使用的所有内存均由平台提供的
LLMAlloc和LLMFree
函数管理。LLM不以任何其他方式进行分配,因此它不会跟踪自己的内存使用情况(并导致无穷递归),这一点非常重要。
转载地址:http://gwubf.baihongyu.com/