一般Unity项目的内存主要分为如下方面:
资源内存
mono内存
dll内存
lua内存
资源标准因项目而异
1)根据项目定位受众的目标设备的性能峰值(比如内存不要超过2G),自上而下的进行规划。
2)若是目标机型的性能越有限,那么项目的资源划分应该更有侧重点,比如项目主要时卖皮肤,又想同屏很多玩家,那么可以在不太影响体验的前提下,挪出其他资源的内存到角色的外观上。
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字段可知是任务相关模块)
1)A模块申请了非常大的a内存,使用完后释放,B模块申请非常小的b内存,位置在之前的a内存内,此时A模块再想申请跟a内存一样大的内存时,由于b内存穿插在之前的a内存内,系统只能另外找一块新的内存分配给A模块,因此a内存内很多空闲空间就未被利用起来了。
1)若是正在使用中的地址,在Objects and Allocations中显示Address并对它进行match定位,它的详情。
下面的案例搜到是unity sbp持有的BundleDetails中的ab数据。
table在GetText的时候,将text的实例push包装成userdata缓存到ObjectTranslator的对象池中。
可以通过在memory profiler里Select Table View-> Raw Data -> Managed Type来赋值筛选使用的类。
推荐情景如下:
使用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只会在同一个参数时第一次声明后放入常量池,而第二次则是新的堆对象。
适用情景:前提该字符串属于常驻(注意是常驻,常量池不能手动控制清理),且多处缓存相同字符串副本时;
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),Array.Empty是C#缓存的空数组,不会产生临时对象。
推荐通过配置表记录这些字符串,资源里面只是序列化该字符串在表中的索引,最终通过代码来赋值同一个字符串实例。
如果这些资源比较通用或者常驻,可以实例化时将资源的字符串通过string.intern重新赋值给资源本身。
1.先分析预定义的经典案例进行内存截取,后针对一些特殊操作细化内存;
2.有了内存截图数据后,应该先分析概要,找出热点,优先优化,收益不高的,优先级次些。
3.自动化流程,跑测采样profiler数据可以交给airtest每日固定时间进行采样。
4.优化内存工具化,比如字节对齐,可以实现工具去扫描比自动调整。
免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删