unity内存优化总结
前言
一般Unity项目的内存主要分为如下方面:
资源内存
mono内存
dll内存
lua内存
资源内存的分析与优化
合理的资源标准
资源标准因项目而异
1.如何定制合理的资源标准
1)根据项目定位受众的目标设备的性能峰值(比如内存不要超过2G),自上而下的进行规划。
2)若是目标机型的性能越有限,那么项目的资源划分应该更有侧重点,比如项目主要时卖皮肤,又想同屏很多玩家,那么可以在不太影响体验的前提下,挪出其他资源的内存到角色的外观上。
mono内存的分析与优化
某个时刻内存占用分析
内存占比合理性
前后时刻内存对比分析
内存分配合理性
方法一:利用memory profiler的compare snapshots功能
1)分别对测试过程的不同时刻进行采样。
2)Single Snapshot中选中的第一个项作为A,切入compare snapshots后选中选择的项作为B,选择完后就会自动生成对比结果。也可以通过调换按钮来让AB对调。
3)可按顺序对diff、type进行group操作,然后在对size排序,可以更看到新增和删除的有多少内存,分别有哪些,大头是什么。
4)
泄露分析
如何才算泄漏
1)一段时间,持续分配,不会消减的对象。
2)
组件实例泄露
组件实例泄露是指:gameObject已调用的Destory,但是memory profiler能捕抓到无名的组件实例对象。(2021版本的memory profiler会右侧的status标记出可能是leaked对象。)
1)可以通过泄露对象的属性辅助定位从属于哪个模块,把分析的范围缩小到该模块的代码。(比如下面的ui.text泄露,从text字段可知是任务相关模块)
碎片分析
fragmented内存是如何产生的?
1)A模块申请了非常大的a内存,使用完后释放,B模块申请非常小的b内存,位置在之前的a内存内,此时A模块再想申请跟a内存一样大的内存时,由于b内存穿插在之前的a内存内,系统只能另外找一块新的内存分配给A模块,因此a内存内很多空闲空间就未被利用起来了。
如何知道一块fragmented内存是来源?
1)若是正在使用中的地址,在Objects and Allocations中显示Address并对它进行match定位,它的详情。
下面的案例搜到是unity sbp持有的BundleDetails中的ab数据。
lua内存的分析与优化
lua闭包引用了C# 组件实例
table在GetText的时候,将text的实例push包装成userdata缓存到ObjectTranslator的对象池中。
dll内存占用优化
可以通过在memory profiler里Select Table View-> Raw Data -> Managed Type来赋值筛选使用的类。
C#的内存优化技巧或者注意点:
善用struct,利用struct代替class。
推荐情景如下:
class/struct的Equals的参数默认为object类型,导致装箱和gc。
使用struct时需要注意它的Equals定义,默认的Equals的参数时object类型,意味值类型传入或被装箱,需要通过自己额外定义一个参数为值类型的Equals来优化。
同理,当class与值类型对比时也会被装箱成object,很隐晦,查起gc难。
public struct Bound
{
public int x,y;
public bool Equals(Bound target);
}
根据目标机器的字节对齐机制对类的字段顺序和类型进行调整以减少实例在内存中的占用。
举个8字节对齐例子:
struct AStruct
{
public ulong a1;
public uint b1;
public ulong c1;
public uint d1;
public bool a2;
public ushort b2;
public ushort c2;
}
优化为
struct AStruct
{
// 调整字段顺序
public ulong a1;
public ulong c1;
public uint b1;
public uint d1;
// 调整类型及实现
public uint _data;
public bool a2 {
get {
return _data & 0xF000
}
}
public ushort b2 {
get {
return _data & 0xFF0
}
}
public ushort c2 {
get {
return _data & 0xF
}
}
}
这种方法一般无损,随对象越多,收益越大,比如大场景的astar寻路数据、地形数据、对象数据等等。
用string.intern代替new string("xxx"),避免在堆上分配重复对象。
string.intern获取的是常量池对象。
new string只会在同一个参数时第一次声明后放入常量池,而第二次则是新的堆对象。
适用情景:前提该字符串属于常驻(注意是常驻,常量池不能手动控制清理),且多处缓存相同字符串副本时;
优化函数参数params或object[]的临时数组分配或装箱
params是个语法糖,等价与参数类型为object[],当参数不为空时就会产生临时数组,比如string.format(object[] args),可根据参数使用量优化额外多定义
string.format(string arg1)
string.format(string arg1, string arg2)
string.format(string arg1, string arg2, sring arg3)
string.format(valueType arg1)
string.format(valueType arg1, valueType arg2)
string.format(valueType arg1, valueType arg2, valueType arg3)
params定义函数参数时,参数为空则string.format()相当于string.format(Array.Empty