UE4内存优化

UE4内存优化

https://zhuanlan.zhihu.com/p/1087487843

移动平台内存参考

分析工具

ADB

adb shell dumpsys meminfo http://com.xxx.xxx 或者pid

衡量指标:PSS/Private Dirty

  1. PSS (Proportional Set Size) = 私有内存占用 + 共享内存占用 / 共享进程数
  2. Private Dirty 值是仅分配给您的应用堆的实际 RAM
  3. Rss Total =私有内存占用 + 共享内存占用
  4. Native 分配的堆内存占用大小
  5. Dalvik Heap 虚拟机堆内存占用大小
  6. Dalvik 虚拟机的 JIT 和 GC 占用的内存
  7. .so mmap native 机器码所占内存
  8. .dex mmap / .jar mmap dex 字节码占用内存和 jar 字节码占用内存

perfetto heapprofile

1. 下载https://raw.githubusercontent.com/google/perfetto/master/tools/heap_profile 保存为heap_profile.py

2. 脚本编辑

setx PERFETTO_BINARY_PATH F:\perfetto\memory\lib #设置符号所在目录
setx PERFETTO_SYMBOLIZER_MODE index
rmdir /s /q output
mkdir output
python3 ./heap_profile.py -n tencent.lolm -c 1000 -o ./output 

3. 将生成的out目录下的symbolized-trace 拖拽到https://ui.perfetto.dev/ 点击采样点进行分析

AndroidStudio

用AS的Profiler查看,能够看到每帧内存使用情况

memreport

命令行里输入memreport -full即可,它会立即生成一份内存快照,存放在ProjectDir/Saved/Profiling/Memreports里以.memreport结尾的文件里

  1. Obj List 当前游戏中的蓝图类及其创造的对象和对应数量
  2. RHI resource memory RHI的Texture,Uniform Buffer,Pixel Buffer,Vertex Buffer
  3. Levels 当前加载的关卡,类似于stat levels指令显示出来的结果
  4. Listing all textures. 当前游戏中用到的贴图信息,包括贴图的大小,格式等等,这块内存的估算还算比较准,并且用来排查一些不合理的、较大的贴图占用也是十分的方便。
  5. ParticleSystems 粒子相关
  6. SkeletalMesh, StaticMesh 骨骼网格体以及静态网格体

LLM(Low Level Memory)

比 memreport 统计数据更加详细精确,实现参考LowLevelMemTracker.cpp

LLM采用的是SCope插桩模式,在需要做内存统计的地方插入LLM_SCOPE(ELLMTag::XXX_TAG)

UE4引擎封装了FMalloc去接管了malloc的内存分配,所以UE4引擎在new一个对象的时候会调用到FMemory::Malloc()函数,通过创建对应平台的内存分配器去进行内存的分配,并通过OnLowLevelAlloc()函数将这部分的内存统计到了LLM系统中去

需要开启宏ALLOW_LOW_LEVEL_MEM_TRACKER_IN_TEST=1

启动添加参数,或者UE4CommandLine.txt中添加

  • -LLM:运行时打开 LLM 统计
  • -LLMCSV:将内存统计信息输出到 CSV 文件中,CSV 文件保存在 Saved\Profiling\LLM 目录下

常见统计分类

  • Total: 总内存
  • ELLMTag::PlatformTotal: 在 Android 上为 RSS,在 iOS 上为 resident_size
  • ELLMTag::Total: PlatformTotal - LLM Overhead,即为上面的内存减去 LLM 本身消耗的内存
  • Untracked: 未知的内存分配,即 LLM 无法追踪的内存
  • ELLMTag::FName
  • ELLMTag::AudioMisc
  • ELLMTag::Networking
  • ELLMTag::Meshes
  • ELLMTag::StaticMesh
  • ELLMTag::SkeletalMesh
  • ELLMTag::InstancedMesh
  • ELLMTag::Stats
  • ELLMTag::Shaders 各种类型 Shander 的内存占用
  • ELLMTag::PSO Pipeline State Object 缓存的内存占用
  • ELLMTag::Textures 纹理相关的内存占用
  • ELLMTag::RenderTargets
  • ELLMTag::Materials: 材质相关的内存,包含 MaterialInterface, MaterialFunction
  • ELLMTag::Particles 特效相关的内存,包含 ParticleSystemComponent
  • ELLMTag::UI: Slate 相关的内存,包含字体,TextureAtlas
  • ELLMTag::PhysX: PhysX 物理相关的内存
  • ELLMTag::LoadMapMisc: 地图加载过程中的内存,分别为以下两个函数
  • ELLMTag::StreamingManager: StreamableManager 相关的内存,资源 streaming 函数执行过程中的内存
  • ELLMTag::GraphicsPlatform: 显存,只在 D3D,Vulkan 上有
  • ELLMTag::AssetRegistry: AssetRegistryModule 内存

LLM的问题

插桩加标记,没有标记到的地方就没办法统计,会被归入到Untracked部分

关于图形API创建用到的部分内存LLM也有做估算,比如Texture以及Buffer部分,但是这部分没有单独分出来统计,不过可以通过stat rhi指令查看

内存优化方案

AssetRegistry

AssetRegistry是Editor的一个子系统,用来收集未加载到内存里的asset信息,当打包Cook完资源后会生成一个assetregistry.bin的文件,包含所有asset的信息,并在启动游戏的时候将这部分信息加载到内存中,游戏资源比较多,内存占用也会比较大,也会影响到FName部分的内存占用。在游戏中不太会用到AssetRegistry的相关接口去查找资源的话,而只需要搜索的目录路径同步进来即可。可通过在DefaultEngine.ini里加入如下配置来省掉这部分内存占用,同时也能缩减包体

[AssetRegistry]
bSerializeAssetRegistry=False

UE4的多语言切换功能会用到AssetRegistry.GetAssetsByPath(),需要通过AssetRegistry.ScanPathsSynchronous()接口将多语言目录同步到AssetRegistry系统中即可

#if WITH_EDITOR
	// Make sure the asset registry has the data we need
	{
		TArray<FString> LocalizedPackagePaths;
		LocalizedPackagePaths.Add(InLocalizedRoot);

		// Set bIsScanningPath to avoid us processing newly added assets from this scan
		TGuardValue<bool> SetIsScanningPath(bIsScanningPath, true);
		AssetRegistry.ScanPathsSynchronous(LocalizedPackagePaths);
	}
#else
	bool bSerializeAssetRegistry = true;
	GConfig->GetBool(TEXT("AssetRegistry"), TEXT("bSerializeAssetRegistry"), bSerializeAssetRegistry, GEngineIni);
	// Make sure the asset registry has the data we need
	if (!bSerializeAssetRegistry)
	{
		TArray<FString> LocalizedPackagePaths;
		LocalizedPackagePaths.Add(InLocalizedRoot);

		// Set bIsScanningPath to avoid us processing newly added assets from this scan
		TGuardValue<bool> SetIsScanningPath(bIsScanningPath, true);
		AssetRegistry.ScanPathsSynchronous(LocalizedPackagePaths);
	}
#endif // WITH_EDITOR

Shader

材质内存的优化思路是尽量减少shader变种,减少shadercode的大小,关掉不必要的Rendering选项,减少动态光源数量等

1. 变体裁剪优化,关闭不必要的rendering选项

减少shader变体组合数量

2. 开启共享材质和库

将大量变体产生的shader有重复的去重,shadercode存入共享库,每个MaterialInstance对象上只存ShaderCode的GUID

3. 低端机discard掉用不到的Material Quality

4. 关闭材质材质Usage减少vertexfactor

OpenGL Shader

UBO

通过glgenbuffer之类的接口创建buffer都存在一个4k左右的基础开销.OpenGL.UseEmulatedUBs=1启用emulated uniform buffers

开启emulated ubo后会将多个uniformbuffer在CommitPackedUniformBuffers函数中合并提交,这样能降低内存的同时也降低了提交次数.

[/Script/Engine.RendererSettings]
OpenGL.UseEmulatedUBs=1

Program LRU

shader这块内存的占用其实可以分为两部分:一个是CPU这块的binary数据,一部分是UseProgram后在GL里加载program后占用的内存。UE4默认是不会清理编译好并且加载到GL里的program。

UE4还是提供了一个Program LRU的功能,通过r.OpenGL.EnableProgramLRUCache开启,可以设置r.OpenGL.ProgramLRUCount以及r.OpenGL.ProgramLRUBinarySize来限制Program最多加载数量以及加载到GL中的最大内存占用. RHICreateBoundShaderState_OnThisThread这个函数有BUG(首次UseProgram时Texture没绑定)高版本已修复

Shader Map

LLM统计得shader内存占用很大部分都是shadermap中shader变体得占用,除了尽量减少shader得变种之外,我们还可以通过设置r.DiscardUnusedQuality将不需要得QualityLevel的shader不加载进来,但是这有一个问题就是不支持在游戏内动态切换QualityLevel。

[/Script/Engine.RendererSettings]
r.DiscardUnusedQuality=True

FileSystem

文件系统相关的内存,主要是文件读取时的缓冲区,比如 Pak 文件。在DefaultEngine.ini加入如下配置来优化这部分的内存占用。

  1. UnloadPakEntryFilenamesIfPossible=true会调用UnloadPakEntryFilenames函数将Pak里的用来检索的Index清理掉,里面创建的Filename字符串也会释放掉,转而用FilenameHashs来代替检索,节省大部分FileSystem的内存占用
  2. ShrinkPakEntriesMemoryUsage开启后,会将Pak里Entries部分的内存进行压缩
  3. DirectoryRootsToKeepInMemoryWhenUnloadingPakEntryFilenames是用来排除那些不想UnloadPakEntryFilenames的目录的
[Pak]
UnloadPakEntryFilenamesIfPossible=True
DirectoryRootsToKeepInMemoryWhenUnloadingPakEntryFilenames="*/Content/Localization/*"
ShrinkPakEntriesMemoryUsage=True

把FindFilesAtPath函数里的条日志暴露出来后运行游戏就能确定要排除的目录

void FindFilesAtPath(ContainerType& OutFiles, const TCHAR* InPath, bool bIncludeFiles = true, bool bIncludeDirectories = false, bool bRecursive = false) const
{
	if ((Directory.StartsWith(MountPoint)) || (MountPoint.StartsWith(Directory)))
	{
		if (bFilenamesRemoved)
		{
			FPlatformMisc::LowLevelOutputDebugString(*(FString("FindFilesAtPath() used when bFilenamesRemoved == true: ") + InPath));
		}
         }
}

GPU Particles

fx.AllowGPUParticles关掉,引擎会用到两张128位1024的RT存gpu particle的position和velocity,占用60MB的内存大小

Program Size

系统最开始初始化获得的内存使用,被LLM推测为可执行程序即初始dll本身的内存, Android端占用较多。通过开启隐藏符号文件来减少这部分开销的。

Texture

Texture Streaming修改预设值

UE4自己的算法计算出当前应当缓存的miplevel,将所有需要流送的纹理缓存大小相加后如果超出预设值,则会按照纹理优先级大小去降低对应纹理用于缓存的miplevel,直到满足预设的大小为止

优先级规则StreamingTexture.cpp

  • 保留地形纹理、强制加载纹理和已经缺失分辨率的纹理
  • 保留在屏幕上可见的mip
  • 保留角色纹理和不占用过多内存的纹理
  • 删掉不可见的mip,先删掉最新看到的mip
[/Script/Engine.RendererSettings]
+CVars=r.Streaming.PoolSize=30 
+CVars=r.Streaming.MipBias=1 
+CVars=r.Streaming.MaxTempMemoryAllowed=5

Memory Bucket

通过Memory Bucket系统去限制不同机器的Texture可加载的最大miplevel

DefaultEngine.ini

[PlatformMemoryBuckets]
DefaultMemoryBucket_MinGB=3
SmallerMemoryBucket_MinGB=2
SmallestMemoryBucket_MinGB=1

在DefaultDeviceProfiles.ini对想做内存控制的TextureGroup进行设置

+TextureLODGroups=(Group=TEXTUREGROUP_World,LODBias_Smaller=1,LODBias_Smallest=2,...)

Texture Compression

ASTC压缩,UE4里默认的是用6x6的块来进行压缩的,当然我们也可以通过ASTC Compression Quality vs Size设置来对默认压缩块的大小进行调整,这里的0-4分别对应的是12x12,10x10,8x8,6x6,4x4,还可以通过ASTC Compression Quality vs Speed来调整压缩速度

贴图单独设置其Compression Quality

Normal Texture

UE4里默认Normal Texture是使用4x4的块来压缩,不会受到ASTC Compression Quality vs Size的设置影响。可以修改FORCED_NORMAL_MAP_COMPRESSION_SIZE_VALUE值来强制修改。同时修改BASE_ASTC_FORMAT_VERSION触发重新cook或者清理后重新cook

UI Texture

UI贴图也都尽量采用可压缩的贴图格式而不是ui interface这种不可压缩格式,贴图的边长必须为4的倍数才会采用可压缩的格式.

Weak Reference

UI贴图比较大,由于默认情况下贴图资源被CDO引用住无法GC掉,可以用弱引用技术的方式来缓解这个问题。如下先设置Brush的bind函数,而不是直接设置Image,然后将要加载进来的texture设置为软引用,通过LoadTexture动态的加载进来即可。

CookCommand 启用TextureMaxSize

2D贴图最大限制最大大小,尽量为2的整数次幂,贴图都通过 TextureGroup 限制最大大小

TextureCube限制大小256设置压缩HDRCompressed

R11G11B10

默认HDR下我们使用FP16的RGBA,把Depth存到SceneColor的A通道。

r.Mobile.SceneColorFormat来调整成R11G11B10减少带宽的占用,可能就会使得某些依赖读回深度的feature发生问题。需要设备支持DepthStencil fetch扩展解决问题,或者使用MRT写深度

mobilecontentscalefactor设置backbuffer

减少带宽,r.ScreenPercentage把单独的3D的分辨率这个会导致upscalepass消耗

Blueprint Cluster

开启后,蓝图资源释放问题,大量的蓝图资源和贴图资源,无法被释放

Mesh LOD

低端设备上只加载特定的LOD, r.FreeSkeletalMeshBuffers释放cpu侧的SkeletalMesh的顶点相关数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值