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),Array.Empty是C#缓存的空数组,不会产生临时对象。

对于大量数据的容器(例如list、queue、dictionary等等)若能预见容量,应该设置预分配容量,以避免每次add元素时触发拓容而导致重新分配数组并Array.CopyTo初始化该数组,并有旧数组的gc。

unity的内存优化技巧或者注意点:

第一次meshFilter.mesh会实例化一份mesh副本。

通过GetComponent获取组件时会有0.3k左右,避免在Update中频繁调用。

尽量避免在资源中序列化字符串,每次实例化该资源,字符串都是从堆里重新实例一份。

  推荐通过配置表记录这些字符串,资源里面只是序列化该字符串在表中的索引,最终通过代码来赋值同一个字符串实例。
  如果这些资源比较通用或者常驻,可以实例化时将资源的字符串通过string.intern重新赋值给资源本身。

Tolua的内存优化

反思工作流程的提效:

  1.先分析预定义的经典案例进行内存截取,后针对一些特殊操作细化内存;
  2.有了内存截图数据后,应该先分析概要,找出热点,优先优化,收益不高的,优先级次些。
  3.自动化流程,跑测采样profiler数据可以交给airtest每日固定时间进行采样。
  4.优化内存工具化,比如字节对齐,可以实现工具去扫描比自动调整。

posted @ 2023-06-07 12:22  昂流  阅读(1257)  评论(0编辑  收藏  举报
//替换成自己路径的js文件 hhttp(s)://static.tctip.com/tctip-1.0.4.min.js